CherryHQ/cherry-studio · critical · Error
Refusing to recursively remove unsafe agent data path: ${age
Error message
Refusing to recursively remove unsafe agent data path: ${agentDataPath} What it means
Thrown by removeAgentDataDirectory as a hard safety guard before recursively deleting an agent data directory. It refuses to proceed when the target path is not a directory or is a symlink, preventing `rm -rf` from following a symlink out of the data root and deleting arbitrary user/system files (a classic symlink-based privilege-escalation/destruction vector).
Source
Thrown at src/main/ai/agents/agentDataDirectory.ts:192
}
for (const filename of AGENT_DATA_FILES) {
const filePath = path.join(agentDataPath, filename)
const fileStat = await lstatIfExists(filePath)
if (fileStat && (!fileStat.isFile || fileStat.isSymbolicLink)) {
throw new Error(`Agent data file must be a real file: ${filePath}`)
}
}
return agentDataPath
}
export async function removeAgentDataDirectory(agentsDataRoot: string, agentId: string): Promise<void> {
const agentDataPath = agentDataDirectoryPath(agentsDataRoot, agentId)
await assertAgentStoragePath(agentsDataRoot, agentDataPath)
const targetStat = await lstatIfExists(agentDataPath)
if (!targetStat) return
if (!targetStat.isDirectory || targetStat.isSymbolicLink) {
throw new Error(`Refusing to recursively remove unsafe agent data path: ${agentDataPath}`)
}
await removeDir(asAbsolutePath(agentDataPath))
}
View on GitHub (pinned to 726446b54c)
Solutions
- Inspect the path manually: `ls -la <agentDataPath>` and `readlink <agentDataPath>`.
- Do NOT bypass the guard. If you have confirmed the entry is a stray symlink/file and safe to delete, remove that specific entry directly with `rm` (not a recursive delete).
- Investigate how the non-directory entry got there (sync service, tampering, buggy migration) and fix the root cause.
- After manual cleanup, retry removeAgentDataDirectory if a real directory remains.
Example fix
// before: agentDataPath is a symlink — recursive remove is (correctly) refused appData/agents/<id> -> /home/user/sensitive // after: remove the symlink explicitly, never bypass with rm -rf unlink appData/agents/<id> # then retry the normal remove flow if needed
Defensive patterns
Strategy: type-guard
Validate before calling
import { lstat } from 'node:fs/promises'
async function canSafeRemove(p: string): Promise<boolean> {
try {
const s = await lstat(p)
return s.isDirectory() && !s.isSymbolicLink()
} catch {
return true // nothing to remove
}
}
if (!(await canSafeRemove(agentDataPath))) {
throw new Error(`Refusing to remove non-directory/symlink: ${agentDataPath}`)
} Type guard
function isSafeRemovableDirectory(stat: import('node:fs').Stats): boolean {
return stat.isDirectory() && !stat.isSymbolicLink()
} Try / catch
try {
await removeAgentDataDirectory(root, agentId)
} catch (e) {
if (e instanceof Error && /unsafe agent data path/.test(e.message)) {
// halt automated cleanup; require manual inspection — never bypass with rm -rf
logger.error('Unsafe agent data path; manual cleanup required', { agentDataPath, error: e })
} else throw e
} Prevention
- Never bypass the guard with a recursive delete on a symlink.
- Investigate how a non-directory entry appeared in the data root.
- Keep the data root under exclusive app control.
When it happens
Trigger: removeAgentDataDirectory(agentsDataRoot, agentId) is called (agent deletion / cleanup) and lstat of the agent data path reports a non-directory entry or a symbolic link.
Common situations: An attacker or sync service replaced the agent dir with a symlink to a sensitive location; the data path was manually turned into a file; attempting to clean up after a corrupted/tampered data store.
Related errors
- Agent data file must be a real file: ${filePath}
- Agent data directory must be a real directory: ${agentDataPa
- Agent memory directory must be a real directory: ${memoryPat
- Required agent prompt file is not a regular file: ${exact}
- Required agent prompt file is not a regular file: ${matchedP
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/b38ee49e4111a660.
Report an issue: GitHub.