{"record":{"id":"a2aa67b8e5a87203","repo":"Mintplex-Labs/anything-llm","slug":"cannot-copy-symbolic-link-source-symlinks-are","errorCode":null,"errorMessage":"Cannot copy symbolic link: ${source}. Symlinks are not allowed during copy operations.","messagePattern":"Cannot copy symbolic link: (.+?)\\. Symlinks are not allowed during copy operations\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/utils/agents/aibitat/plugins/filesystem/copy-file.js","lineNumber":9,"sourceCode":"const fs = require(\"fs/promises\");\nconst path = require(\"path\");\nconst filesystem = require(\"./lib.js\");\n\nasync function copyRecursive(source, destination) {\n  const lstat = await fs.lstat(source);\n\n  if (lstat.isSymbolicLink()) {\n    throw new Error(\n      `Cannot copy symbolic link: ${source}. Symlinks are not allowed during copy operations.`\n    );\n  }\n\n  if (lstat.isDirectory()) {\n    await fs.mkdir(destination, { recursive: true });\n    const entries = await fs.readdir(source);\n    for (const entry of entries) {\n      await copyRecursive(\n        path.join(source, entry),\n        path.join(destination, entry)\n      );\n    }\n  } else {\n    await fs.copyFile(source, destination);\n  }\n}\n","sourceCodeStart":1,"sourceCodeEnd":27,"githubUrl":"https://github.com/Mintplex-Labs/anything-llm/blob/3aec848f2885144aa8f1e53b9731a04310d5d558/server/utils/agents/aibitat/plugins/filesystem/copy-file.js#L1-L27","documentation":"Thrown by copyRecursive in the agent filesystem copy-file plugin when fs.lstat identifies the source path as a symbolic link. This is a deliberate security guard: recursively copying attacker-controlled trees that may contain symlinks enables path-traversal/arbitrary-file-write attacks when an agent copies into the workspace, so symlinks abort the whole copy instead of being followed or preserved.","triggerScenarios":"An agent using the copy-file filesystem tool on any directory tree that contains a symlink — node_modules with linked packages, /usr/sharealternatives-style links, dotfile directories with links, or a user deliberately symlink-ing shared assets into the copy source.","commonSituations":"Asking the agent to copy a project folder containing node_modules (pnpm/yarn workspaces use symlinks heavily); copying configuration trees where symlinks are routine; copying from a mounted volume whose contents include host-created links.","solutions":["Copy only the concrete files/subdirectories you need, excluding the path containing the symlink.","Resolve the link yourself first (fs.realpath) and copy the real target's contents as regular files.","Restructure the source so shared content is duplicated rather than symlinked before asking the agent to copy it.","Treat this abort as by-design: do not attempt to bypass it in agent-driven flows."],"exampleFix":"// before: agent tool call\ncopyFile({ source: \"/data/project\", destination: \"/data/backup\" })\n// /data/project contains a symlink -> Error: Cannot copy symbolic link...\n\n// after: copy only real entries\nconst fs = require(\"fs/promises\");\nfor (const entry of await fs.readdir(\"/data/project\", { withFileTypes: true })) {\n  if (entry.isSymbolicLink()) continue; // skip links\n  await fs.cp(`/data/project/${entry.name}`, `/data/backup/${entry.name}`, { recursive: true });\n}","handlingStrategy":"validation","validationCode":"const fs = require(\"fs/promises\");\n\nasync function containsSymlink(p) {\n  const st = await fs.lstat(p);\n  if (st.isSymbolicLink()) return true;\n  if (!st.isDirectory()) return false;\n  for (const entry of await fs.readdir(p)) {\n    if (await containsSymlink(require(\"path\").join(p, entry))) return true;\n  }\n  return false;\n}\n\nif (await containsSymlink(sourceDir))\n  throw new Error(\"Refusing to copy: source tree contains a symlink\");","typeGuard":null,"tryCatchPattern":"try {\n  await copyFileTool({ source, destination });\n} catch (e) {\n  if (/Cannot copy symbolic link/.test(e.message)) {\n    // skip links and copy only real entries, or resolve the target manually\n    return copyRealEntriesOnly(source, destination);\n  }\n  throw e;\n}","preventionTips":["Pre-scan source trees with lstat before asking the agent to copy them; skip or resolve symlinks explicitly.","Expect pnpm/yarn-workspace node_modules and system config dirs to contain links — exclude them from agent copy operations.","Treat this guard as a security feature: don't disable or bypass it for agent-driven paths."],"tags":["filesystem","security","symlink","agent-plugin","path-traversal"],"backgroundTag":"symlink-not-allowed","analyzedSha":"3aec848f2885144aa8f1e53b9731a04310d5d558","analyzedAt":"2026-08-18T10:02:21.017Z","contentChangedAt":"2026-08-18T10:02:21.017Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}