deer-flow/docs/superpowers/plans/2026-06-19-guardrail-request-attribution.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

187 lines
6.0 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 运行时用户上下文与归因字段补充 — 实现计划
**Goal:** 将服务端认证后的用户上下文和运行时归因信息传递给 `GuardrailProvider``user_id``user_role``oauth_provider``oauth_id``thread_id``run_id``tool_call_id`
**Architecture:** Gateway 只从 `request.state.user` 注入可信用户上下文run worker 复用现有 `runtime.context` 传递机制GuardrailMiddleware 只消费 `ToolCallRequest.runtime.context`,不依赖 FastAPI request、DB 或 auth repository。
**Tech Stack:** Python 3.12+, dataclasses, LangGraph `ToolCallRequest.runtime.context`, pytest
---
## 文件结构
| 操作 | 文件 | 职责 |
|------|------|------|
| Modify | `backend/app/gateway/services.py` | 注入 authenticated user context |
| Modify | `backend/packages/harness/deerflow/guardrails/provider.py` | `GuardrailRequest` 扩字段 |
| Modify | `backend/packages/harness/deerflow/guardrails/middleware.py` | 从 runtime context 填充字段 |
| Modify | `backend/tests/test_setup_agent_e2e_user_isolation.py` | 覆盖 Gateway → runtime context |
| Modify | `backend/tests/test_guardrail_middleware.py` | 覆盖 runtime context → GuardrailRequest |
| Modify | `backend/docs/GUARDRAILS.md` | 更新 custom provider 示例 |
---
## Task 1: Gateway 注入 authenticated user context
**Files:**
- Modify: `backend/app/gateway/services.py`
- [x] **Step 1: 扩展 `inject_authenticated_user_context()`**
在已有 `runtime_context["user_id"] = str(user_id)` 后追加:
```python
runtime_context["user_role"] = getattr(user, "system_role", None)
runtime_context["oauth_provider"] = getattr(user, "oauth_provider", None)
runtime_context["oauth_id"] = getattr(user, "oauth_id", None)
```
**约束:**
- 只读取 `request.state.user`,不信任 client `body.context`
- `INTERNAL_SYSTEM_ROLE` 仍保持跳过注入。
- local user 的 OAuth 字段允许为 `None`
- [x] **Step 2: 扩展 config assembly 测试**
`TestConfigAssembly` 中覆盖:
- authenticated user 的 `user_role/oauth_provider/oauth_id` 进入 runtime context。
- client-supplied `user_id/user_role/oauth_provider/oauth_id` 不覆盖 server-authenticated user。
---
## Task 2: GuardrailRequest 扩展字段
**Files:**
- Modify: `backend/packages/harness/deerflow/guardrails/provider.py`
- [x] **Step 1: 在 `GuardrailRequest` 末尾新增 optional 字段**
```python
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
```
**约束:**
- 不修改 `GuardrailDecision`
- 不修改现有 provider protocol。
- 所有字段 optional保持向后兼容。
---
## Task 3: GuardrailMiddleware 从 runtime context 填充
**Files:**
- Modify: `backend/packages/harness/deerflow/guardrails/middleware.py`
- [x] **Step 1: 安全读取 `ToolCallRequest.runtime.context`**
```python
runtime = getattr(request, "runtime", None)
context = getattr(runtime, "context", None) if runtime is not None else None
context = context if isinstance(context, dict) else {}
```
- [x] **Step 2: 构造 `GuardrailRequest` 时填充字段**
```python
GuardrailRequest(
tool_name=str(request.tool_call.get("name", "")),
tool_input=request.tool_call.get("args", {}),
agent_id=self.passport,
thread_id=context.get("thread_id"),
timestamp=datetime.now(UTC).isoformat(),
user_id=context.get("user_id"),
user_role=context.get("user_role"),
oauth_provider=context.get("oauth_provider"),
oauth_id=context.get("oauth_id"),
run_id=context.get("run_id"),
tool_call_id=request.tool_call.get("id"),
)
```
**约束:**
- runtime/context 缺失时字段为 `None`
- `thread_id` 是已有字段,本次补填。
- 不在 GuardrailMiddleware 中访问 request/auth/DB。
---
## Task 4: 文档与示例
**Files:**
- Modify: `backend/docs/GUARDRAILS.md`
- [x] **Step 1: 增加 Runtime Attribution 小节**
说明以下字段及用途:
- `user_id`:稳定审计归因。
- `user_role`:简单 role-based policy。
- `oauth_provider/oauth_id`:外部身份 provider/subject 映射local user 可为空。
- `thread_id/run_id/tool_call_id`run/tool-call 定位。
- [x] **Step 2: 更新 Context-Aware Provider 示例**
示例 provider 只接收 `policy_path/audit_path`,把 `GuardrailRequest` 归一化成 provider-defined policy context并展示
```python
"role_tool_key": f"{request.user_role or ''}:{request.tool_name}"
```
示例 policy 使用:
```yaml
field: role_tool_key
operator: eq
value: admin:bash
```
**文案约束:**
- 不声称 DeerFlow 内置该 YAML schema。
- 不写成对标 AGT。
- AGT/OPA/Cedar 只作为可选 policy engine 示例。
---
## Task 5: 测试与验证
- [x] **Step 1: 运行目标测试**
```bash
cd backend
PYTHONPATH=. uv run pytest \
tests/test_guardrail_middleware.py::TestGuardrailRequestAttribution \
tests/test_setup_agent_e2e_user_isolation.py::TestConfigAssembly -v
```
Expected:
```text
15 passed
```
- [x] **Step 2: 验证覆盖点**
| 测试 | 覆盖 |
|------|------|
| `test_authenticated_user_context_includes_role_and_oauth_identity` | Gateway 注入完整用户上下文 |
| `test_client_supplied_user_id_is_overridden` | 防止客户端伪造身份字段 |
| `test_authenticated_user_context_present` | GuardrailRequest 接收用户上下文 |
| `test_all_attribution_fields_present` | 用户上下文与 run/tool-call 归因同时存在 |
| missing context tests | 字段缺失时为 `None` |
---
## 自检清单
1. **Scope:** 改动保持在 authenticated runtime context 与 Guardrail provider 入参,不新增治理系统。
2. **Security:** `user_role/oauth_provider/oauth_id` 只从服务端认证态注入,不信任客户端 context。
3. **Compatibility:** 新字段全部 optional现有 provider 不读取时行为不变。
4. **Docs:** Guardrails 文档示例使用 role-based policy不再建议手写 UUID 策略。
5. **Tests:** 目标测试已通过:`15 passed`