Honor view-all off: only own sessions and personal workspace.

Shared project and channel sessions were still visible via canViewSystemSessions when the toggle was off; tighten ACL and copy so off means own only.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-16 23:52:02 +08:00
parent 7c899942fb
commit ef57e05942
5 changed files with 55 additions and 61 deletions

View file

@ -67,7 +67,7 @@ window.__ModuleLoader__.load({
"ui.add": "添加",
"ui.department": "部门",
"ui.viewAllSessionsTitle": "查看全部会话",
"ui.viewAllSessionsIntro": "超级管理员默认可见全部会话(含渠道)。关闭后仅看自己的;侧栏与 @ 提及规则相同。",
"ui.viewAllSessionsIntro": "默认可见全部会话(含渠道与共享工作区)。关闭后仅看自己名下的会话与个人工作区。",
"ui.viewAllSessionsToggle": "显示所有人的会话",
"ui.viewAllSessionsOn": "已开启:可见全部会话",
"ui.viewAllSessionsOff": "已关闭:仅可见自己的会话",
@ -207,7 +207,7 @@ window.__ModuleLoader__.load({
"ui.add": "Add",
"ui.department": "Department",
"ui.viewAllSessionsTitle": "View all sessions",
"ui.viewAllSessionsIntro": "Super admins see all sessions by default (including channels). Turn off to only see your own; sidebar and @ mentions share the same rule.",
"ui.viewAllSessionsIntro": "All sessions are visible by default (including channels and shared workspaces). Turn off to only see sessions you own and your personal workspace.",
"ui.viewAllSessionsToggle": "Show everyone’s sessions",
"ui.viewAllSessionsOn": "On: all sessions visible",
"ui.viewAllSessionsOff": "Off: only your own sessions",

View file

@ -465,21 +465,20 @@ export function createSessionAccess({
const root = getWorkspaceRoot()
const wid = ws.id ?? ws.workspaceId
const path = ws.path
// View-all off: only the caller's provisioned user workspace — not shared
// project folders (chatgpt/harness/…) or channel roots. Those stay under view-all.
if (userWorkspaces.isUserPath(empNo, path, root)
|| (userWorkspaces.get(empNo)?.workspaceId
&& String(userWorkspaces.get(empNo).workspaceId) === String(wid))) {
return true
}
// Channel / bot / shared harness workspaces live outside user-workspaces.
if (identity.permissions?.canViewSystemSessions && isOutsideUserWorkspaceRoot(path, root)) {
return true
}
return false
}
/**
* Owner stamp OR cwd/workspace under the caller's provisioned path.
* Settings roles also see unowned system/channel sessions (cwd outside user-workspaces).
* Owner stamp OR cwd under the caller's provisioned path.
* View-all off means only those — no "unowned outside user-workspaces" leak
* (shared project sessions were incorrectly treated as channel/system).
*/
const canAccessSession = (sessionId, identity, rowHint) => {
if (!identity || !empOf(identity)) return false
@ -495,21 +494,13 @@ export function createSessionAccess({
const foreignOwner = !!(owner && String(owner) !== String(empNo))
// IM/channel (and other host-internal) sessions: often unstamped + bot cwd.
// Let admin+ see those; never leak another user's stamped private session.
if (!foreignOwner && !owner && identity.permissions?.canViewSystemSessions
&& isOutsideUserWorkspaceRoot(cwd, root)) {
return true
}
const registry = resolveRegistry()
if (!registry || typeof registry.list !== 'function') return false
let workspaces = []
try { workspaces = registry.list() || [] } catch { return false }
for (const ws of workspaces) {
if (!isVisibleWorkspace(identity, ws)) continue
// Foreign-owned sessions must not become visible via channel/system workspaces.
if (foreignOwner && isOutsideUserWorkspaceRoot(ws?.path, root)) continue
if (foreignOwner) continue
if (workspaceContainsSession(ws, sessionId)) return true
}
return false
@ -963,10 +954,7 @@ ctx.inject(['workspaceController'], (wctx) => {
&& String(userWorkspaces.get(empNo).workspaceId) === String(wid))) {
return true
}
if (identity.permissions?.canViewSystemSessions
&& isOutsideUserWorkspaceRoot(ws?.path, root)) {
return true
}
// View-all off: hide shared/channel workspaces from the sidebar partitions.
return false
}
@ -992,20 +980,6 @@ ctx.inject(['workspaceController'], (wctx) => {
const allowed = new Set()
const mapped = userWorkspaces.get(empNo)?.workspaceId
if (mapped != null) allowed.add(String(mapped))
// Keep channel/bot workspaces for admin+ (same rule as allowWorkspace).
if (identity?.permissions?.canViewSystemSessions) {
try {
const root = getWorkspaceRoot()
const registry = resolveRegistry()
const list = typeof registry?.list === 'function' ? (registry.list() || []) : []
for (const ws of list) {
const id = ws?.id ?? ws?.workspaceId
if (id != null && isOutsideUserWorkspaceRoot(ws?.path, root)) {
allowed.add(String(id))
}
}
} catch { /* ignore */ }
}
return {
...frame,
workspaceIds: (frame.workspaceIds || []).filter((id) => allowed.has(String(id))),

View file

@ -62,7 +62,7 @@ export const MESSAGES = {
// privacy / session visibility
'ui.viewAllSessionsTitle': '查看全部会话',
'ui.viewAllSessionsIntro': '超级管理员默认可见全部会话(含渠道)。关闭后仅看自己的;侧栏与 @ 提及规则相同。',
'ui.viewAllSessionsIntro': '默认可见全部会话(含渠道与共享工作区)。关闭后仅看自己名下的会话与个人工作区。',
'ui.viewAllSessionsToggle': '显示所有人的会话',
'ui.viewAllSessionsOn': '已开启:可见全部会话',
'ui.viewAllSessionsOff': '已关闭:仅可见自己的会话',
@ -214,7 +214,7 @@ export const MESSAGES = {
'ui.department': 'Department',
'ui.viewAllSessionsTitle': 'View all sessions',
'ui.viewAllSessionsIntro': 'Super admins see all sessions by default (including channels). Turn off to only see your own; sidebar and @ mentions share the same rule.',
'ui.viewAllSessionsIntro': 'All sessions are visible by default (including channels and shared workspaces). Turn off to only see sessions you own and your personal workspace.',
'ui.viewAllSessionsToggle': 'Show everyone’s sessions',
'ui.viewAllSessionsOn': 'On: all sessions visible',
'ui.viewAllSessionsOff': 'Off: only your own sessions',

View file

@ -1,6 +1,6 @@
{
"name": "uds-auth",
"version": "0.2.12",
"version": "0.2.13",
"description": "UDS authentication plugin for DeepSeek Harness with multi-tenant session isolation",
"type": "module",
"main": "lib/index.js",

View file

@ -119,17 +119,57 @@ describe('createSessionAccess', () => {
assert.equal(canAccessSession('legacy-other', user, { cwd: '/ws/u2' }), false)
})
it('admin can see unowned channel/system sessions outside user-workspaces', () => {
it('view-all off: admin only sees own sessions, not channel/shared workspaces', () => {
const store = new RolesStore()
store._roles.set('u1', ROLES.ADMIN)
store.setViewAllSessions('u1', false)
const accessOff = createSessionAccess({
sessionAcl: { getOwner: () => null },
userWorkspaces: {
get: () => null,
isUserPath: () => false,
get: (empNo) => (empNo === 'u1' ? { path: '/ws/u1', workspaceId: 'ws-u1' } : null),
isUserPath: (empNo, candidate) => {
if (empNo !== 'u1' || !candidate) return false
const path = String(candidate).replace(/\\/g, '/')
return path === '/ws/u1' || path.startsWith('/ws/u1/')
},
},
getWorkspaceRoot: () => '/ws',
getWorkspaceRegistry: () => ({
list: () => ([
{ id: 'ws-u1', path: '/ws/u1', sessionIds: ['mine'] },
{ id: 'bot-ws', path: '/bots/whatsapp', sessionIds: ['ch-1'] },
{ id: 'shared', path: '/project/chatgpt', sessionIds: ['peer'] },
]),
}),
rolesStore: store,
})
const admin = {
empNo: 'u1',
role: 'admin',
permissions: computePermissions('admin', { viewAllSessions: false }),
}
assert.equal(accessOff.canSeeAll(admin), false)
assert.equal(accessOff.canAccessSession('mine', admin, { cwd: '/ws/u1/a' }), true)
assert.equal(accessOff.canAccessSession('ch-1', admin, { cwd: '/bots/whatsapp' }), false)
assert.equal(accessOff.canAccessSession('peer', admin, { cwd: '/project/chatgpt' }), false)
assert.equal(accessOff.isVisibleWorkspace(admin, { id: 'ws-u1', path: '/ws/u1' }), true)
assert.equal(accessOff.isVisibleWorkspace(admin, { id: 'bot-ws', path: '/bots/whatsapp' }), false)
assert.equal(accessOff.isVisibleWorkspace(admin, { id: 'shared', path: '/project/chatgpt' }), false)
store.setViewAllSessions('u1', true)
assert.equal(accessOff.canAccessSession('ch-1', admin, { cwd: '/bots/whatsapp' }), true)
assert.equal(accessOff.canAccessSession('peer', admin, { cwd: '/project/chatgpt' }), true)
})
it('view-all off: foreign-owned session stays hidden even in shared workspace', () => {
const store = new RolesStore()
store._roles.set('u1', ROLES.ADMIN)
store.setViewAllSessions('u1', false)
const ownersMap = new Map([['ch-owned', 'u2']])
const accessOwned = createSessionAccess({
sessionAcl: { getOwner: (id) => ownersMap.get(String(id)) || null },
userWorkspaces: { get: () => null, isUserPath: () => false },
getWorkspaceRoot: () => '/ws',
getWorkspaceRegistry: () => ({ list: () => [] }),
rolesStore: store,
})
@ -138,27 +178,7 @@ describe('createSessionAccess', () => {
role: 'admin',
permissions: computePermissions('admin', { viewAllSessions: false }),
}
const user = {
empNo: 'u1',
role: 'user',
permissions: computePermissions('user'),
}
assert.equal(accessOff.canAccessSession('ch-1', admin, { cwd: '/bots/whatsapp' }), true)
assert.equal(accessOff.canAccessSession('ch-1', user, { cwd: '/bots/whatsapp' }), false)
assert.equal(accessOff.canAccessSession('s-peer', admin, { cwd: '/ws/u2/x' }), false)
const ownersMap = new Map([['ch-owned', 'u2']])
const accessOwned = createSessionAccess({
sessionAcl: { getOwner: (id) => ownersMap.get(String(id)) || null },
userWorkspaces: { get: () => null, isUserPath: () => false },
getWorkspaceRoot: () => '/ws',
getWorkspaceRegistry: () => ({ list: () => [] }),
rolesStore: store,
})
assert.equal(accessOwned.canAccessSession('ch-owned', admin, { cwd: '/bots/wa' }), false)
assert.equal(accessOff.isVisibleWorkspace(admin, { id: 'bot-ws', path: '/bots/whatsapp' }), true)
assert.equal(accessOff.isVisibleWorkspace(user, { id: 'bot-ws', path: '/bots/whatsapp' }), false)
})
it('live rolesStore prefs override stale identity.permissions', () => {