{"record":{"id":"b38ee49e4111a660","repo":"CherryHQ/cherry-studio","slug":"refusing-to-recursively-remove-unsafe-agent-data-p","errorCode":null,"errorMessage":"Refusing to recursively remove unsafe agent data path: ${agentDataPath}","messagePattern":"Refusing to recursively remove unsafe agent data path: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"src/main/ai/agents/agentDataDirectory.ts","lineNumber":192,"sourceCode":"  }\n\n  for (const filename of AGENT_DATA_FILES) {\n    const filePath = path.join(agentDataPath, filename)\n    const fileStat = await lstatIfExists(filePath)\n    if (fileStat && (!fileStat.isFile || fileStat.isSymbolicLink)) {\n      throw new Error(`Agent data file must be a real file: ${filePath}`)\n    }\n  }\n  return agentDataPath\n}\n\nexport async function removeAgentDataDirectory(agentsDataRoot: string, agentId: string): Promise<void> {\n  const agentDataPath = agentDataDirectoryPath(agentsDataRoot, agentId)\n  await assertAgentStoragePath(agentsDataRoot, agentDataPath)\n  const targetStat = await lstatIfExists(agentDataPath)\n  if (!targetStat) return\n  if (!targetStat.isDirectory || targetStat.isSymbolicLink) {\n    throw new Error(`Refusing to recursively remove unsafe agent data path: ${agentDataPath}`)\n  }\n  await removeDir(asAbsolutePath(agentDataPath))\n}\n","sourceCodeStart":174,"sourceCodeEnd":196,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/agents/agentDataDirectory.ts#L174-L196","documentation":"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).","triggerScenarios":"removeAgentDataDirectory(agentsDataRoot, agentId) is called (agent deletion / cleanup) and lstat of the agent data path reports a non-directory entry or a symbolic link.","commonSituations":"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.","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."],"exampleFix":"// before: agentDataPath is a symlink — recursive remove is (correctly) refused\nappData/agents/<id> -> /home/user/sensitive\n\n// after: remove the symlink explicitly, never bypass with rm -rf\nunlink appData/agents/<id>\n# then retry the normal remove flow if needed","handlingStrategy":"type-guard","validationCode":"import { lstat } from 'node:fs/promises'\nasync function canSafeRemove(p: string): Promise<boolean> {\n  try {\n    const s = await lstat(p)\n    return s.isDirectory() && !s.isSymbolicLink()\n  } catch {\n    return true // nothing to remove\n  }\n}\nif (!(await canSafeRemove(agentDataPath))) {\n  throw new Error(`Refusing to remove non-directory/symlink: ${agentDataPath}`)\n}","typeGuard":"function isSafeRemovableDirectory(stat: import('node:fs').Stats): boolean {\n  return stat.isDirectory() && !stat.isSymbolicLink()\n}","tryCatchPattern":"try {\n  await removeAgentDataDirectory(root, agentId)\n} catch (e) {\n  if (e instanceof Error && /unsafe agent data path/.test(e.message)) {\n    // halt automated cleanup; require manual inspection — never bypass with rm -rf\n    logger.error('Unsafe agent data path; manual cleanup required', { agentDataPath, error: e })\n  } else throw e\n}","preventionTips":["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."],"tags":["filesystem","security","symlink","destructive","agents"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}