docs(ops): record permissions/uploads bug-hunt audit and short-term task

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
weichen
2026-09-20 07:05:06 +00:00
co-authored by Cursor
parent c8dbefe8a1
commit 6800a2af29
4 changed files with 114 additions and 7 deletions
@@ -0,0 +1,48 @@
---
title: "系统权限设置与项目协作多项缺陷排查"
status: done
created: 2026-09-20
updated: 2026-09-20
severity: high
---
# 系统权限设置与项目协作多项缺陷排查
## 现象
用户报告:成员管理勾选无效、系统设置「允许编制人兼任审核批准」400、PDF 预览被拦截、任务 runs 403 刷屏、发起编制被标 NETWORK_ERROR、注册/建号 rollback、改密后刷新再强制改密、退出重登出现同名重复项目、无项目仍可对话、右侧工具栏默认展开且不记忆。
## 根因分析
| # | 根因 | 关键路径 |
|---|------|----------|
| 1 | CSP 缺 `frame-src`,blob iframe fallback 到 `default-src 'self'` | `internal/server/cors.go` |
| 2 | PdfViewer 同源 iframe 直连,触发 `X-Frame-Options: DENY` 原生拦截页 | `web/src/components/doc/preview/renderers/PdfViewer.tsx` |
| 3 | 前端未传 `expected_revision`(该 key 后端强制 CAS) | `web/src/api/systemAdmin.ts` / SettingsSection |
| 4 | `ProjectSettingsApiError` 字段名与消费方 `httpStatus`/`errorCode` 不一致 | `web/src/api/projectSettingsError.ts` |
| 5 | task-run view 仅放行创建者/owner;前端 403 无停止轮询 | `task_run_auth.go` / `App.tsx` |
| 6 | `CanManageProjectMembers` 未读细粒度权限位 | `internal/authz/service.go` |
| 7 | memStore `ListProjectsForUser` 忽略成员表 | `internal/projectworkflow/store.go` |
| 8 | `user_role_bindings` 插入吞错 → commit rollback;handler 一律 400 | `internal/auth/pg_store.go` |
| 9 | 嵌套 `user.mustChangePassword` OR 合并复活 + fetchMe 未回写 session | `authUserMap.ts` / WorkbenchView / useAuthenticatedWorkspaceSync |
| 10 | `projects.name` 无唯一约束 + Create 无幂等键;logout 不清 projects 缓存 | `service.go` / migration 00056 / App logout |
| 11 | `Project()<=0` 时跳过项目访问校验 | `handlers.go` / `sse.go` / `stage_access_helpers.go` |
| 12 | `rightCollapsed` 默认 false 且无持久化 | `AppShell.tsx` / `storage.ts` |
## 严重度
`high`
## 关联整改
- Short-term: `.doc/ops/short-term/2026-09-20-bug-hunt-permissions-uploads.md`
- 分支: `fix/bug-hunt-2026-09-20-permissions-and-uploads`
## 证据
- 产品决策:新增 `project:manage_members`;无项目禁止对话并置灰发送按钮;同创建人内项目名唯一 + `client_key` 幂等。
- 迁移:`00055_...project-manage-members-permission.sql`、`00056_...project-create-idempotency.sql`
## 开放问题
- 无(本批范围内)
+7 -7
View File
@@ -43,7 +43,7 @@ AuthN column is **heuristic**; negative-test pointers filled where known.
| POST | `/api/v1/auth/register` | `s.handleAuthRegister` | `server.go` | handler | see suite | | POST | `/api/v1/auth/register` | `s.handleAuthRegister` | `server.go` | handler | see suite |
| GET | `/api/v1/auth/sessions` | `s.handleAuthSessions` | `server.go` | handler | see suite | | GET | `/api/v1/auth/sessions` | `s.handleAuthSessions` | `server.go` | handler | see suite |
| DELETE | `/api/v1/auth/sessions/{id}` | `s.handleAuthSessionByID` | `server.go` | handler | see suite | | DELETE | `/api/v1/auth/sessions/{id}` | `s.handleAuthSessionByID` | `server.go` | handler | see suite |
| POST | `/api/v1/chat` | `s.handleChat` | `server.go` | session+project | see suite | | POST | `/api/v1/chat` | `s.handleChat` | `server.go` | session+project (ERR_NO_PROJECT if unbound + zero memberships) | see suite |
| GET | `/api/v1/chat/categories` | `s.handleListChatCategories` | `server.go` | session+project | see suite | | GET | `/api/v1/chat/categories` | `s.handleListChatCategories` | `server.go` | session+project | see suite |
| GET | `/api/v1/chat/dates` | `s.handleListChatDates` | `server.go` | session+project | see suite | | GET | `/api/v1/chat/dates` | `s.handleListChatDates` | `server.go` | session+project | see suite |
| GET | `/api/v1/chat/digests` | `s.handleListDailyDigests` | `server.go` | session+project | see suite | | GET | `/api/v1/chat/digests` | `s.handleListDailyDigests` | `server.go` | session+project | see suite |
@@ -60,8 +60,8 @@ AuthN column is **heuristic**; negative-test pointers filled where known.
| GET | `/api/v1/chat/search` | `s.handleChatSearch` | `server.go` | session+project | see suite | | GET | `/api/v1/chat/search` | `s.handleChatSearch` | `server.go` | session+project | see suite |
| GET | `/api/v1/chat/sessions` | `s.handleListChatSessions` | `server.go` | session+project | see suite | | GET | `/api/v1/chat/sessions` | `s.handleListChatSessions` | `server.go` | session+project | see suite |
| POST | `/api/v1/chat/sessions` | `s.handleUpsertChatSession` | `server.go` | session+project | see suite | | POST | `/api/v1/chat/sessions` | `s.handleUpsertChatSession` | `server.go` | session+project | see suite |
| GET | `/api/v1/chat/stream` | `s.handleChatStream` | `server.go` | session+project | see suite | | GET | `/api/v1/chat/stream` | `s.handleChatStream` | `server.go` | session+project (ERR_NO_PROJECT if unbound + zero memberships) | see suite |
| POST | `/api/v1/chat/stream` | `s.handleChatStream` | `server.go` | session+project | see suite | | POST | `/api/v1/chat/stream` | `s.handleChatStream` | `server.go` | session+project (ERR_NO_PROJECT if unbound + zero memberships) | see suite |
| GET | `/api/v1/chat/turns/{client_turn_id}` | `s.handleGetChatTurn` | `server.go` | session+project | see suite | | GET | `/api/v1/chat/turns/{client_turn_id}` | `s.handleGetChatTurn` | `server.go` | session+project | see suite |
| GET | `/api/v1/chat/unread` | `s.handleChatUnread` | `server.go` | session+project | see suite | | GET | `/api/v1/chat/unread` | `s.handleChatUnread` | `server.go` | session+project | see suite |
| GET | `/api/v1/context/cases/recommend` | `s.handleRecommendCases` | `server.go` | handler | see suite | | GET | `/api/v1/context/cases/recommend` | `s.handleRecommendCases` | `server.go` | handler | see suite |
@@ -131,10 +131,10 @@ AuthN column is **heuristic**; negative-test pointers filled where known.
| GET | `/api/v1/tasks/run/stream` | `s.handleRunTaskStream` | `server.go` | session+project | see suite | | GET | `/api/v1/tasks/run/stream` | `s.handleRunTaskStream` | `server.go` | session+project | see suite |
| POST | `/api/v1/tasks/run/stream` | `s.handleRunTaskStream` | `server.go` | session+project | see suite | | POST | `/api/v1/tasks/run/stream` | `s.handleRunTaskStream` | `server.go` | session+project | see suite |
| POST | `/api/v1/tasks/runs` | `s.handleCreateTaskRun` | `server.go` | session+project | see suite | | POST | `/api/v1/tasks/runs` | `s.handleCreateTaskRun` | `server.go` | session+project | see suite |
| GET | `/api/v1/tasks/runs/{run_id}` | `s.handleGetTaskRun` | `server.go` | session+project | see suite | | GET | `/api/v1/tasks/runs/{run_id}` | `s.handleGetTaskRun` | `server.go` | session+project member (view) | see suite |
| POST | `/api/v1/tasks/runs/{run_id}/cancel` | `s.handleCancelTaskRun` | `server.go` | session+project | see suite | | POST | `/api/v1/tasks/runs/{run_id}/cancel` | `s.handleCancelTaskRun` | `server.go` | session+run owner or project owner | see suite |
| GET | `/api/v1/tasks/runs/{run_id}/events` | `s.handleTaskRunEvents` | `server.go` | session+project | see suite | | GET | `/api/v1/tasks/runs/{run_id}/events` | `s.handleTaskRunEvents` | `server.go` | session+project member (view) | see suite |
| POST | `/api/v1/tasks/runs/{run_id}/resume` | `s.handleResumeTaskRun` | `server.go` | session+project | see suite | | POST | `/api/v1/tasks/runs/{run_id}/resume` | `s.handleResumeTaskRun` | `server.go` | session+run owner or project owner | see suite |
| GET | `/api/v1/workflow-instances/{id}` | `s.handleGetWorkflowInstance` | `server.go` | session+project | see suite | | GET | `/api/v1/workflow-instances/{id}` | `s.handleGetWorkflowInstance` | `server.go` | session+project | see suite |
| POST | `/api/v1/workflow-instances/{id}/stages/{stage}/advance` | `s.handleAdvanceStage` | `server.go` | session+project | see suite | | POST | `/api/v1/workflow-instances/{id}/stages/{stage}/advance` | `s.handleAdvanceStage` | `server.go` | session+project | see suite |
| GET | `/api/v1/workflow-instances/{id}/stages/{stage}/readiness` | `s.handleGetStageReadiness` | `server.go` | session+project | see suite | | GET | `/api/v1/workflow-instances/{id}/stages/{stage}/readiness` | `s.handleGetStageReadiness` | `server.go` | session+project | see suite |
+1
View File
@@ -15,6 +15,7 @@
| owner | task | branch | worktree | mutex | status | | owner | task | branch | worktree | mutex | status |
|-------|------|--------|----------|-------|--------| |-------|------|--------|----------|-------|--------|
| Cursor-Auto | 2026-09-20-bug-hunt-permissions-uploads | fix/bug-hunt-2026-09-20-permissions-and-uploads | - | authz+web+projectworkflow | in_progress |
| Cursor | audit-b1-data-consistency | fix/audit-b1-data-consistency | - | docgov+workflow+chat | done | | Cursor | audit-b1-data-consistency | fix/audit-b1-data-consistency | - | docgov+workflow+chat | done |
| Cursor | audit-b2-authz-hardening | fix/audit-b2-authz-hardening | - | authz | done | | Cursor | audit-b2-authz-hardening | fix/audit-b2-authz-hardening | - | authz | done |
| Cursor | audit-b3-sse-events | fix/audit-b3-sse-events | - | chat+events | done | | Cursor | audit-b3-sse-events | fix/audit-b3-sse-events | - | chat+events | done |
@@ -0,0 +1,58 @@
---
title: "修复系统权限设置与项目协作多项缺陷"
status: in_progress
created: 2026-09-20
updated: 2026-09-20
owner: Cursor-Auto
branch: fix/bug-hunt-2026-09-20-permissions-and-uploads
worktree: "-"
mutex: authz+web+projectworkflow
---
# 修复系统权限设置与项目协作多项缺陷
## 目标
修复两轮排查确认的 12 处后端/前端缺陷(CSP/PDF、系统设置 CAS、任务错误误分类、task-run 403、成员管理 RBAC、注册吞错、强制改密复发、项目重名、无项目可对话、右侧栏不记忆等)。
## 范围
- CSP `frame-src` + PDF blob 预览
- 系统设置 `expected_revision` 全链路
- `ProjectSettingsApiError` httpStatus/errorCode 别名
- task-run 查看放宽为项目成员;前端 403/404 停轮询
- `project:manage_members` 权限位(迁移 00055)
- memStore 成员可见性
- CreateUser 吞错 / 400·500 分类 / LooksInternal
- 强制改密 session 嵌套字段 OR 复活
- CreateProject `client_key` 幂等 + `(creator, name)` 唯一(迁移 00056)
- 无项目禁止对话 + 发送按钮置灰
- 右侧工具栏默认收起并持久化
## 非目标
- 全局项目名唯一(仅同创建人内唯一)
- 放宽 `frame-ancestors` / `X-Frame-Options`
## 步骤
1. [x] 后端 CSP / authz / auth / projectworkflow / chat 入口
2. [x] 前端 PDF / settings / 错误别名 / 轮询 / RBAC UI / 改密 / 建项幂等 / InputArea / AppShell
3. [x] 迁移 00055 / 00056 + 单测
4. [ ] `go vet` / `go test -short` / `npm test` / `npm run build`
5. [ ] 占集成槽位合回 `dev`
## 验收标准
- [x] 代码与契约补齐(见 audit 笔记)
- [ ] 短测与前端构建通过
- [ ] 合入 `dev`
## 相关链接
- Audit: `.doc/audit/2026-09-20-bug-hunt-permissions-uploads.md`
- Plan: `.cursor/plans/修复系统权限设置与项目协作多项缺陷_ff3242f7.plan.md`
## 证据 / 备注
分支:`fix/bug-hunt-2026-09-20-permissions-and-uploads`