docs: record duty forgery and ETag overwrite risks
Document the privilege-escalation and silent-overwrite hazards; keep the reject-routing task in_progress pending real UI rerun. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
title: "文档职责伪造与保存时重取 ETag 可覆盖并发正文"
|
||||||
|
status: in_progress
|
||||||
|
created: 2026-09-13
|
||||||
|
updated: 2026-09-13
|
||||||
|
severity: critical
|
||||||
|
---
|
||||||
|
|
||||||
|
# 文档职责伪造与保存时重取 ETag 可覆盖并发正文
|
||||||
|
|
||||||
|
## 现象
|
||||||
|
|
||||||
|
静态审查与 E2E UI-13 路径暴露两类产品缺陷:
|
||||||
|
|
||||||
|
1. **职责提升**:审核人 B 或项目外陌生人打开受控文档后,工作台把其当成编制人,出现正文编辑 / 送审动作。
|
||||||
|
2. **并发覆盖**:两窗口同开同一修订时,后保存方在点击保存瞬间重新 GET 最新 ETag,If-Match 对准已更新账本,409 不会出现,先保存方的正文被静默覆盖。
|
||||||
|
|
||||||
|
## 根因分析
|
||||||
|
|
||||||
|
| 层 | 问题 | 风险 |
|
||||||
|
|---|---|---|
|
||||||
|
| API | `ListWorkflowDocumentsByProject` / `GET projects/{id}/documents` 的 summary 不投影 `document_records` 的 author/reviewer/approver id+name | 前端无权威职责,只能猜 |
|
||||||
|
| 前端 merge | `mergeWorkflowDocumentsFromServer` 用当前登录用户名伪造 `author`,审核/批准写死「李主管」「王总监」 | B 登录后 `author===自己`,权限矩阵显示编制人 |
|
||||||
|
| 角色解析 | `currentDocRole` 比 name,失败默认 `'author'` | 未知身份 fail-open,陌生人获得编辑/送审 |
|
||||||
|
| 保存 | `handleSaveBlockEdit` 在 PATCH 前 `getRevisionState` 取最新 ETag | 打开时基线被丢弃;后写覆盖先写 |
|
||||||
|
| E2E | UI-13b 在 A1 完整保存后才打开 A2 编辑器 | 不是真实竞争,无法钉住上述 ETag 缺陷 |
|
||||||
|
|
||||||
|
安全含义:职责伪造是**越权写**(viewer/reviewer 可改受控正文并送审)。ETag 刷新是**完整性破坏**(并发编辑丢失且无冲突提示)。
|
||||||
|
|
||||||
|
## 严重度
|
||||||
|
|
||||||
|
`critical`
|
||||||
|
|
||||||
|
## 关联整改
|
||||||
|
|
||||||
|
- Remediation: [../remediation/2026-09-13-document-revision-duty-etag-overwrite.md](../remediation/2026-09-13-document-revision-duty-etag-overwrite.md)
|
||||||
|
- Short-term: [../ops/short-term/2026-09-13-document-revision-e2e-reject-routing.md](../ops/short-term/2026-09-13-document-revision-e2e-reject-routing.md)
|
||||||
|
- 分支: `fix/document-revision-e2e-reject-routing`
|
||||||
|
|
||||||
|
## 证据
|
||||||
|
|
||||||
|
- 代码(修复前):`internal/docgov/project_docs.go` summary 无职责列;`web/src/lib/deliverableSync.ts` `author: fallbackUser.name`;`web/src/App.tsx` 默认 `'author'` 与保存前 `getRevisionState`
|
||||||
|
- 单测钉住:B/陌生人不成 author;stale hydrate ETag 用于 PATCH
|
||||||
|
- 本轮不跑真实 E2E、不提交 evidence assets
|
||||||
|
|
||||||
|
## 开放问题
|
||||||
|
|
||||||
|
- 占槽后真实 UI-13a/13b 重跑;CURRENT 仍 `in_progress`,不得标 `done`
|
||||||
+2
-1
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
## 焦点
|
## 焦点
|
||||||
|
|
||||||
1. **DOCREV-RT**: [E2E UI-05b 钉住 intake 并收紧历史比较/冲突](short-term/2026-09-13-document-revision-e2e-reject-routing.md)(`fix/document-revision-e2e-reject-routing`:不改产品逻辑;不重跑 E2E / 不操作容器)
|
1. **DOCREV-RT**: [E2E UI-05b + 职责/ETag 产品修复](short-term/2026-09-13-document-revision-e2e-reject-routing.md)(`fix/document-revision-e2e-reject-routing`:权威职责 + 打开时基线 ETag;不重跑 E2E / 不操作容器;仍 `in_progress`)
|
||||||
2. **CHAT**: [刷新恢复整改已实施](short-term/2026-09-13-chat-history-refresh-remediation.md)(待合 dev + PI E2E 补录)
|
2. **CHAT**: [刷新恢复整改已实施](short-term/2026-09-13-chat-history-refresh-remediation.md)(待合 dev + PI E2E 补录)
|
||||||
3. **CI**: `TEST_DATABASE_URL` + 真实 PI E2E 实录;INTENT 发布 blocker 见 conversation-intent short-term
|
3. **CI**: `TEST_DATABASE_URL` + 真实 PI E2E 实录;INTENT 发布 blocker 见 conversation-intent short-term
|
||||||
|
|
||||||
@@ -60,3 +60,4 @@
|
|||||||
| 行动表状态假阳性审计 | [../audit/2026-09-13-document-revision-quality-status-false-positive.md](../audit/2026-09-13-document-revision-quality-status-false-positive.md) |
|
| 行动表状态假阳性审计 | [../audit/2026-09-13-document-revision-quality-status-false-positive.md](../audit/2026-09-13-document-revision-quality-status-false-positive.md) |
|
||||||
| 待确认报告选择失败审计 | [../audit/2026-09-13-document-revision-pending-report-selection.md](../audit/2026-09-13-document-revision-pending-report-selection.md) |
|
| 待确认报告选择失败审计 | [../audit/2026-09-13-document-revision-pending-report-selection.md](../audit/2026-09-13-document-revision-pending-report-selection.md) |
|
||||||
| UI-05b 拒绝路由失败审计 | [../audit/2026-09-13-document-revision-e2e-reject-routing.md](../audit/2026-09-13-document-revision-e2e-reject-routing.md) |
|
| UI-05b 拒绝路由失败审计 | [../audit/2026-09-13-document-revision-e2e-reject-routing.md](../audit/2026-09-13-document-revision-e2e-reject-routing.md) |
|
||||||
|
| 职责伪造与 ETag 覆盖审计 | [../audit/2026-09-13-document-revision-duty-etag-overwrite.md](../audit/2026-09-13-document-revision-duty-etag-overwrite.md) |
|
||||||
|
|||||||
@@ -13,16 +13,17 @@ mutex: document-revision-runtime-ui
|
|||||||
|
|
||||||
## 目标
|
## 目标
|
||||||
|
|
||||||
第五轮 UI-05b 首错修复:拒绝用例 prompt 必须带「当前受控文档《用户初始需求收集表》」,并在本轮 `POST /tasks/runs` 上严格断言 `task_key==='intake'` 且 `doc_id===controlledDocId`。同步收紧 UI-10/12/13/14 真实用户路径。不改产品业务代码。
|
第五轮 UI-05b 首错修复:拒绝用例 prompt 必须带「当前受控文档《用户初始需求收集表》」,并在本轮 `POST /tasks/runs` 上严格断言 `task_key==='intake'` 且 `doc_id===controlledDocId`。同步收紧 UI-10/12/13/14 真实用户路径。本轮追加产品修复:权威文档职责 + 打开时基线 ETag。
|
||||||
|
|
||||||
## 范围
|
## 范围
|
||||||
|
|
||||||
- `web/e2e/document-revision-runtime.spec.ts`
|
- `internal/docgov/project_docs.go`(summary 职责投影)
|
||||||
- 文档:audit / CURRENT(mutex `document-revision-runtime-ui`)
|
- `web/src` 职责字段、`resolveCurrentDocRole`、hydrate/PATCH ETag
|
||||||
|
- `web/e2e/document-revision-runtime.spec.ts`(UI-13b 真实竞争)
|
||||||
|
- 文档:audit / remediation / CURRENT(mutex `document-revision-runtime-ui`)
|
||||||
|
|
||||||
## 非目标
|
## 非目标
|
||||||
|
|
||||||
- 不改 `templateMatch` / Toolbar / decision / PATCH
|
|
||||||
- 不降低已有业务断言或超时;不伪造、不直接打 API
|
- 不降低已有业务断言或超时;不伪造、不直接打 API
|
||||||
- 不触碰/提交 `.doc/test-reports/assets`;不操作容器;本轮不重跑 E2E
|
- 不触碰/提交 `.doc/test-reports/assets`;不操作容器;本轮不重跑 E2E
|
||||||
- 不改 FEATURE-MATRIX
|
- 不改 FEATURE-MATRIX
|
||||||
@@ -33,7 +34,9 @@ mutex: document-revision-runtime-ui
|
|||||||
2. [x] UI-05b prompt + tasks/runs 契约断言 + 真实打开拒绝
|
2. [x] UI-05b prompt + tasks/runs 契约断言 + 真实打开拒绝
|
||||||
3. [x] UI-10 历史 dialog 内选 A/B 比较并关闭;UI-12 项目不可见;UI-13 拆成审核人只读 + 双 A 冲突;UI-14 同时锁 v/r
|
3. [x] UI-10 历史 dialog 内选 A/B 比较并关闭;UI-12 项目不可见;UI-13 拆成审核人只读 + 双 A 冲突;UI-14 同时锁 v/r
|
||||||
4. [x] 仅修 05a/05b/10 遗留弹窗对 UI-06 的确切遮挡
|
4. [x] 仅修 05a/05b/10 遗留弹窗对 UI-06 的确切遮挡
|
||||||
5. [ ] 占槽后真实 UI 重跑(本轮不做)
|
5. [x] 产品修复:权威职责 + viewer fail-closed + hydrate 基线 ETag
|
||||||
|
6. [x] UI-13b:A1 保存前 A2 编辑器已打开并填入文本;两窗口同一 r/etag
|
||||||
|
7. [ ] 占槽后真实 UI 重跑(本轮不做)
|
||||||
|
|
||||||
## 验收标准
|
## 验收标准
|
||||||
|
|
||||||
@@ -41,11 +44,15 @@ mutex: document-revision-runtime-ui
|
|||||||
- [x] UI-10 不点全局工具条「比较」
|
- [x] UI-10 不点全局工具条「比较」
|
||||||
- [x] UI-12 不接受「项目可见但只查 marker」
|
- [x] UI-12 不接受「项目可见但只查 marker」
|
||||||
- [x] UI-13a/13b 结果分别记录;B 不作正文编辑者
|
- [x] UI-13a/13b 结果分别记录;B 不作正文编辑者
|
||||||
|
- [x] B/陌生人不会变 author;A 仍 author(Go + 前端单测)
|
||||||
|
- [x] stale hydrate ETag 用于 PATCH(不能用最新 ETag 掩盖冲突)
|
||||||
- [x] web tests + `tsc`/build + `git diff --check`
|
- [x] web tests + `tsc`/build + `git diff --check`
|
||||||
- [ ] 真实 UI-05b+ 后段 PASS 后才可将本任务标 `done`
|
- [ ] 真实 UI-05b+ 后段及 UI-13a/13b PASS 后才可将本任务标 `done`
|
||||||
|
|
||||||
## 相关链接
|
## 相关链接
|
||||||
|
|
||||||
- Audit: [../../audit/2026-09-13-document-revision-e2e-reject-routing.md](../../audit/2026-09-13-document-revision-e2e-reject-routing.md)
|
- Audit (05b 路由): [../../audit/2026-09-13-document-revision-e2e-reject-routing.md](../../audit/2026-09-13-document-revision-e2e-reject-routing.md)
|
||||||
|
- Audit (职责/ETag): [../../audit/2026-09-13-document-revision-duty-etag-overwrite.md](../../audit/2026-09-13-document-revision-duty-etag-overwrite.md)
|
||||||
|
- Remediation: [../../remediation/2026-09-13-document-revision-duty-etag-overwrite.md](../../remediation/2026-09-13-document-revision-duty-etag-overwrite.md)
|
||||||
- Evidence: [../../test-reports/assets/2026-09-13-document-revision-runtime-ui/schema30-ui05b-reject-routing-failure](../../test-reports/assets/2026-09-13-document-revision-runtime-ui/schema30-ui05b-reject-routing-failure)
|
- Evidence: [../../test-reports/assets/2026-09-13-document-revision-runtime-ui/schema30-ui05b-reject-routing-failure](../../test-reports/assets/2026-09-13-document-revision-runtime-ui/schema30-ui05b-reject-routing-failure)
|
||||||
- 前序: [2026-09-13-document-revision-e2e-pending-flow.md](./2026-09-13-document-revision-e2e-pending-flow.md)
|
- 前序: [2026-09-13-document-revision-e2e-pending-flow.md](./2026-09-13-document-revision-e2e-pending-flow.md)
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
title: "权威文档职责 + 打开时基线 ETag"
|
||||||
|
status: in_progress
|
||||||
|
created: 2026-09-13
|
||||||
|
updated: 2026-09-13
|
||||||
|
---
|
||||||
|
|
||||||
|
# 权威文档职责 + 打开时基线 ETag
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
链到 [../audit/2026-09-13-document-revision-duty-etag-overwrite.md](../audit/2026-09-13-document-revision-duty-etag-overwrite.md)。
|
||||||
|
|
||||||
|
陌生人/审核人被抬成编制人可改受控正文;保存时重取最新 ETag 会掩盖 409 并覆盖并发编辑。
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
1. 列表 summary 返回台账 author/reviewer/approver 的 id+name;前端保留权威字段,禁止用当前登录用户伪造 author。
|
||||||
|
2. `currentDocRole` 按权威 user id 解析;name 仅兼容旧本地文档;未知身份 fail-closed 为 `viewer`;viewer 无编辑/审核/批准动作。
|
||||||
|
3. 受控正文 hydrate 同时取 revision-state ETag 写入 AST/缓存;人工 PATCH 只用该基线;成功后用响应 `state.etag` 更新;冲突保留编辑器与文本。无账本 bootstrap/freeform 仍可用 `0:0`。
|
||||||
|
4. UI-13b:A1 保存前两窗口已加载相同 r/etag,且 A2 编辑器已打开并填入文本。
|
||||||
|
|
||||||
|
## 分波次计划
|
||||||
|
|
||||||
|
| 波次 | 内容 | 验收 |
|
||||||
|
|------|------|------|
|
||||||
|
| Wave 1 | Go summary 投影职责 + 单测 | B 不是 author;A 仍是 author |
|
||||||
|
| Wave 2 | 前端 merge / `UserRole=viewer` / `resolveCurrentDocRole` | B/陌生人不成 author;viewer 无动作 |
|
||||||
|
| Wave 3 | hydrate 基线 ETag + PATCH 不再 GET | stale ETag 单测;不能用最新 ETag 掩盖冲突 |
|
||||||
|
| Wave 4 | UI-13b 真实竞争脚本 | A2 先开编辑器再 A1/A2 保存 |
|
||||||
|
| Wave 5 | 占槽真实 UI(本轮不做) | UI-13a/13b 实战 PASS 后才可 done |
|
||||||
|
|
||||||
|
## 验收标准
|
||||||
|
|
||||||
|
- [x] Go `ListWorkflowDocumentsIncludesAuthoritativeDuties`
|
||||||
|
- [x] 前端 merge + `resolveCurrentDocRole` 单测
|
||||||
|
- [x] stale hydrate ETag 用于保存(纯函数 + hydrate 单测)
|
||||||
|
- [x] UI-13b 脚本不降级(本轮不跑真实 E2E)
|
||||||
|
- [ ] 真实 UI 重跑 PASS
|
||||||
|
|
||||||
|
## 相关链接
|
||||||
|
|
||||||
|
- Audit: [../audit/2026-09-13-document-revision-duty-etag-overwrite.md](../audit/2026-09-13-document-revision-duty-etag-overwrite.md)
|
||||||
|
- Short-term: [../ops/short-term/2026-09-13-document-revision-e2e-reject-routing.md](../ops/short-term/2026-09-13-document-revision-e2e-reject-routing.md)
|
||||||
Reference in New Issue
Block a user