{"record":{"id":"4b4adf957d089d0e","repo":"CherryHQ/cherry-studio","slug":"invalid-agent-id-for-data-directory-agentid","errorCode":null,"errorMessage":"Invalid agent id for data directory: ${agentId}","messagePattern":"Invalid agent id for data directory: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"src/main/ai/agents/agentDataDirectory.ts","lineNumber":94,"sourceCode":"    throw new Error(`Agent storage path resolves outside its root: ${target}`)\n  }\n}\n\n/** Ensure a Data/Agents path is a real directory contained by the Agent storage root. */\nexport async function ensureAgentStorageDirectory(agentsDataRoot: string, targetPath: string): Promise<void> {\n  await ensureDir(asAbsolutePath(path.resolve(agentsDataRoot)))\n  await assertAgentStoragePath(agentsDataRoot, targetPath)\n  await ensureDir(asAbsolutePath(path.resolve(targetPath)))\n  await assertAgentStoragePath(agentsDataRoot, targetPath)\n  const targetStat = await lstat(asAbsolutePath(path.resolve(targetPath)))\n  if (!targetStat.isDirectory || targetStat.isSymbolicLink) {\n    throw new Error(`Agent storage directory must be a real directory: ${targetPath}`)\n  }\n}\n\nfunction assertAgentId(agentId: string): void {\n  if (!agentId || agentId === '.' || agentId === '..' || agentId.toLowerCase() === 'system' || /[\\\\/]/.test(agentId)) {\n    throw new Error(`Invalid agent id for data directory: ${agentId}`)\n  }\n}\n\nexport function agentDataDirectoryPath(agentsDataRoot: string, agentId: string): string {\n  assertAgentId(agentId)\n  return path.join(agentsDataRoot, agentId)\n}\n\nasync function ensureEmptyFile(filePath: string): Promise<void> {\n  const existing = await lstatIfExists(filePath)\n  if (existing) {\n    if (!existing.isFile || existing.isSymbolicLink) {\n      throw new Error(`Agent data file must be a real file: ${filePath}`)\n    }\n    return\n  }\n  try {\n    const handle = await open(filePath, 'wx', 0o600)","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/agents/agentDataDirectory.ts#L76-L112","documentation":"Thrown by assertAgentId when the agentId is empty, the special traversal names '.' or '..', the reserved name 'system' (case-insensitive), or contains any path separator (/ or \\). agentId is used to construct the agent data directory via path.join(agentsDataRoot, agentId), so these values would either traverse the filesystem or collide with the reserved system namespace. The guard runs in agentDataDirectoryPath before any path is built.","triggerScenarios":"Calling agentDataDirectoryPath (directly or via ensureAgentDataDirectory/createAgentDataDirectory) with an empty agentId; an agentId of 'system'; an id containing '/' or '\\' (e.g. 'foo/bar' or 'a\\b'); or the literal '.' / '..' strings.","commonSituations":"A caller passes an unsanitized external id; an import/migration produced an id with a path separator; a generated id collided with 'system'; an empty string from a form field or failed id generation; cross-platform id with a Windows backslash.","solutions":["Generate agent ids from a safe alphabet (UUIDs, base64url, alnum) that cannot contain separators or be empty.","Validate ids at the point of creation (agent creation form/service) against the same rules before persisting.","If migrating data, sanitize or reject ids containing path separators before they reach the filesystem layer.","Reserve 'system' at the id-generation layer so users/imports cannot mint it."],"exampleFix":"// before\nawait ensureAgentDataDirectory(root, userInput) // userInput may be 'a/b' or ''\n\n// after\nconst id = crypto.randomUUID() // safe, separator-free\nawait ensureAgentDataDirectory(root, id)","handlingStrategy":"validation","validationCode":"import path from 'node:path'\nfunction assertAgentId(agentId: string): void {\n  if (!agentId || agentId === '.' || agentId === '..' || agentId.toLowerCase() === 'system' || /[\\\\/]/.test(agentId)) {\n    throw new Error(`Invalid agent id: ${agentId}`)\n  }\n}\nassertAgentId(candidate)","typeGuard":"const isValidAgentId = (id: string): boolean =>\n  !!id && id !== '.' && id !== '..' && id.toLowerCase() !== 'system' && !/[\\\\/]/.test(id)","tryCatchPattern":null,"preventionTips":["Generate agent ids from a safe alphabet (UUID / base64url) — never raw user input.","Validate ids at agent-creation time against the same rules before persisting.","Sanitize imported/migrated ids that contain separators before they reach the filesystem layer.","Reserve 'system' at the id-generation layer so it can never be minted."],"tags":["security","path-traversal","validation","agent","filesystem"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}