ソースを参照

修复子账号未读中心访问并验证账号隔离与 CSRF

Samien 1 ヶ月 前
親
コミット
9121d09899

+ 1 - 0
docs/releases/v0.4.78.md

@@ -7,6 +7,7 @@
 - 新增 Bot 密码管理界面,支持加密存储、手动解锁、短时任务授权、撤销、加密备份及主密码轮换。
 - 增加受控网站表单、SSH 与 SFTP 凭据适配器;仅执行用户预先配置并授权的操作,返回脱敏结果,结果不明确时不自动重试。
 - 新增统一未读中心,按聊天模式汇总未读消息,支持跳转与已读处理;macOS Dock 角标使用服务端确认的未读状态。
+- 修复子账号访问未读中心的权限遗漏,保留账号隔离与已读操作校验。
 - 改善跨端已读回执与聊天同步,处理并发追加、部分提交及写入结果不明确的情况。
 - 修复发送与排队时的聊天布局跳动,兼容不同聊天日期格式,减少流式输出时重复扫描未读历史。
 - 修正上下文用量统计、上传文件名称与命令行安装行为。

+ 2 - 0
src/server/subaccountAccess.ts

@@ -1,6 +1,8 @@
 /** Owner-scoped Bot endpoints. Keep native and administrative surfaces denied. */
 export function isWorkspaceAccountPath(method: string, path: string): boolean {
   const read = method === 'GET' || method === 'HEAD'
+  if (read && path === '/api/app/unread') return true
+  if (method === 'POST' && path === '/api/app/unread/read') return true
   if(read&&path==='/api/app/vault')return true
   if(method==='POST'&&/^\/api\/app\/vault\/(initialize|unlock|lock|rotate|backup|restore|entries|leases)$/.test(path))return true
   if(['PUT','DELETE'].includes(method)&&/^\/api\/app\/vault\/entries\/[0-9a-f-]{36}$/.test(path))return true

+ 8 - 0
tests/server/subaccountRegistration.test.ts

@@ -69,6 +69,14 @@ describe('subaccount registration and approval through real middleware', () => {
     expect(snapshot.agents.map((a: any) => a.id)).toEqual([create.body.agent.id])
     expect(snapshot.conversations).toHaveLength(1)
     expect((await call(admin, 'get', '/api/app/workspace/snapshot')).body.agents).toEqual([])
+    // The shared unread centre is owner-scoped, including Bot-only accounts.
+    expect((await call(child, 'get', '/api/app/unread').expect(200)).body.total).toBe(0)
+    await call(child, 'head', '/api/app/unread').expect(200)
+    await call(child, 'post', '/api/app/unread/read').send({ items: [] }).expect(403)
+    await call(child, 'post', '/api/app/unread/read', login.body.csrfToken).send({ items: [] }).expect(200)
+    const adminAgent = (await call(admin, 'post', '/api/app/agents', csrf).send({ name: '管理员 Bot', profile: 'allowed' }).expect(201)).body.agent
+    const adminConversation = (await call(admin, 'get', '/api/app/conversations').expect(200)).body.conversations.find((c: any) => c.memberIds.includes(adminAgent.id))
+    await call(child, 'post', '/api/app/unread/read', login.body.csrfToken).send({ items: [{ mode: 'bot', id: adminConversation.id, messages: [] }] }).expect(404)
     await call(child, 'get', '/api/app/workspace/projects').expect(200)
     await call(child, 'get', '/api/app/workspace/collaboration').expect(200)
     await call(child, 'get', '/api/app/workspace/memory-jobs').expect(200)