{"record":{"id":"8a10f10d430b2512","repo":"Mintplex-Labs/anything-llm","slug":"access-denied-parent-directory-outside-allowed-d","errorCode":null,"errorMessage":"Access denied - parent directory outside allowed directories.","messagePattern":"Access denied - parent directory outside allowed directories\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/utils/agents/aibitat/plugins/filesystem/lib.js","lineNumber":464,"sourceCode":"        );\n      }\n      return realPath;\n    } catch (error) {\n      if (error.code === \"ENOENT\") {\n        const parentDir = path.dirname(absolute);\n        try {\n          const realParentPath = await fs.realpath(parentDir);\n          const normalizedParent = this.#normalizePath(realParentPath);\n          if (\n            !this.#isPathWithinAllowedDirectories(\n              normalizedParent,\n              this.#allowedDirectories\n            )\n          ) {\n            console.log(\n              `[validatePath] Access denied - parent directory outside allowed directories: ${realParentPath} not in ${this.#allowedDirectories.join(\", \")}`\n            );\n            throw new Error(\n              `Access denied - parent directory outside allowed directories.`\n            );\n          }\n          return absolute;\n        } catch {\n          throw new Error(`Parent directory does not exist: ${parentDir}`);\n        }\n      }\n      throw error;\n    }\n  }\n\n  /**\n   * Gets detailed file statistics.\n   * @param {string} filePath - Path to the file\n   * @returns {Promise<Object>} File statistics\n   */\n  async getFileStats(filePath) {","sourceCodeStart":446,"sourceCodeEnd":482,"githubUrl":"https://github.com/Mintplex-Labs/anything-llm/blob/3aec848f2885144aa8f1e53b9731a04310d5d558/server/utils/agents/aibitat/plugins/filesystem/lib.js#L446-L482","documentation":"Thrown by validatePath when the target file does not exist yet (ENOENT from realpath) and the realpath of its parent directory resolves outside the allowed directories. This branch guards file-creation paths: before creating a new file, the library verifies the directory that will hold it is inside the sandbox after resolving symlinks. It is the parent-directory analogue of the symlink-escape check.","triggerScenarios":"write_file to sandbox-dir/link/new.txt where 'link' is a symlink whose realpath is outside the allowed roots; creating a file under a mount point that resolves (via symlink or bind) to a host path outside STORAGE_DIR/anythingllm-fs; Docker volume layouts where the workspace dir is itself a link to /host/mnt.","commonSituations":"Docker-compose mounts a host directory through a symlinked path; users 'relocate' the sandbox by symlinking storage/anythingllm-fs elsewhere; NFS/automount paths whose realpath differs from the mount point.","solutions":["Resolve the parent chain with readlink -f <parent-dir> and confirm the physical path is under an allowed directory; remove the offending symlink in the parent chain.","Move the real directory into the sandbox and use its physical path for writes.","If the physical location must be used, add it to the library's allowed directories at initialization."],"exampleFix":"# before\nln -s /mnt/hostdata /app/storage/anythingllm-fs/out\nwrite_file({ path: \"out/new.txt\", content: \"x\" })  # throws\n\n# after (real directory inside the sandbox)\nmv /mnt/hostdata /app/storage/anythingllm-fs/out\nwrite_file({ path: \"out/new.txt\", content: \"x\" })  # ok","handlingStrategy":"try-catch","validationCode":"const fs = require(\"fs\").promises;\nconst path = require(\"path\");\nasync function parentResolvesInsideSandbox(fileOps, target) {\n  const allowed = fileOps.getAllowedDirectories();\n  const parent = path.dirname(path.resolve(target));\n  const realParent = await fs.realpath(parent); // throws if parent missing -> error 323 instead\n  return allowed.some((r) => realParent === r || realParent.startsWith(r + path.sep));\n}","typeGuard":null,"tryCatchPattern":"try {\n  await fileOps.writeFileContent(p, content);\n} catch (e) {\n  if (e.message.includes(\"parent directory outside allowed directories\")) {\n    // resolve and inspect path.dirname chain; fix symlinks, do not widen the sandbox automatically\n    throw new Error(`Write target's parent resolves outside sandbox: ${p}`);\n  }\n  throw e;\n}","preventionTips":["For new-file writes, verify readlink -f of the containing directory stays under the sandbox root.","Avoid Docker volume layouts where the sandbox path is a symlink to a host mount.","Keep file-creation paths flat relative to the allowed root where possible."],"tags":["filesystem","symlink","parent-directory","sandbox","file-creation"],"backgroundTag":"sandbox-path-denied","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"}