Fix cron cwd for super_admin and harden IM channel job isolation.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-10 10:00:59 +08:00
parent b35bfde557
commit b1bb0067a0
16 changed files with 714 additions and 49 deletions

View file

@ -5,6 +5,7 @@
import { formatInZone, resolveTodayAt } from './scheduler.js'
import {
assertDeliveryAllowedForIdentity,
deliveryLine,
jobVisibleToPeer,
resolveCallerPeer,
@ -125,7 +126,14 @@ export function resolveCreateCwd(args, exec, { peer = null } = {}) {
const sessionCwd = callerWorkingDirectory(exec)
const passed = typeof args?.cwd === 'string' ? args.cwd.trim() : ''
// IM peers cannot point scheduled Agents at arbitrary host paths.
if (peer?.botId) return sessionCwd
if (peer?.botId) {
if (!sessionCwd) {
const error = new Error('IM scheduled jobs require a non-empty session working directory')
error.code = 'INVALID_CWD'
throw error
}
return sessionCwd
}
if (passed) return passed
return sessionCwd
}
@ -148,6 +156,7 @@ function empNoFromUserWorkspacePath(cwd) {
/**
* Resolve web caller identity for ownership. IM peers keep conversation scoping.
* Prefer uds-auth role lookup so super_admin keeps canViewAllSessions (cwd sanitize).
*/
export function resolveToolIdentity(exec, service) {
const uds = typeof service?.getUdsAuth === 'function' ? service.getUdsAuth() : null
@ -160,11 +169,28 @@ export function resolveToolIdentity(exec, service) {
empNo = empNoFromUserWorkspacePath(callerWorkingDirectory(exec))
}
if (!empNo) return null
const id = String(empNo)
if (typeof uds?.resolveIdentityForEmpNo === 'function') {
const resolved = uds.resolveIdentityForEmpNo(id)
if (resolved?.empNo) {
return {
empNo: String(resolved.empNo),
displayName: String(resolved.displayName || resolved.empNo),
role: resolved.role || 'user',
permissions: {
canViewAllSessions: !!resolved.permissions?.canViewAllSessions,
canCreateWorkspace: !!resolved.permissions?.canCreateWorkspace,
},
workspacePath: resolved.workspacePath || uds.getProvisionedWorkspacePath?.(id) || null,
}
}
}
return {
empNo: String(empNo),
displayName: String(empNo),
empNo: id,
displayName: id,
role: 'user',
permissions: { canViewAllSessions: false },
workspacePath: uds?.getProvisionedWorkspacePath?.(id) || null,
}
}
@ -295,8 +321,12 @@ export function cronToolDefinitions(service, deps = {}) {
const dshIm = getDshIm()
try {
const peer = await resolveCallerPeer(exec, dshIm)
const identity = resolveToolIdentity(exec, service)
// Channel chats scope by peer; do not stamp empNo (avoids sanitize clobbering bot cwd).
const identity = peer?.botId ? null : resolveToolIdentity(exec, service)
const delivery = await resolveCreateDelivery(args, exec, { dshIm })
if (identity && !peer?.botId) {
assertDeliveryAllowedForIdentity(delivery, identity)
}
const origin = await resolveCreateOrigin(args, exec, { dshIm })
const agentPreset = await resolveCreateAgentPreset(args, exec, {
dshIm,
@ -314,7 +344,7 @@ export function cronToolDefinitions(service, deps = {}) {
mirrorToSession: args.mirror_to_session === true,
...origin ? { origin } : {},
agentPreset,
}, identity)
}, identity, { fromImPeer: !!peer?.botId })
return { job }
} catch (error) {
const tz = args.timezone || args.time_zone || 'Asia/Shanghai'