{"record":{"id":"19b2395317182214","repo":"Mintplex-Labs/anything-llm","slug":"access-denied-path-outside-allowed-directories","errorCode":null,"errorMessage":"Access denied - path outside allowed directories.","messagePattern":"Access denied - path outside allowed directories\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/utils/agents/aibitat/plugins/filesystem/lib.js","lineNumber":429,"sourceCode":"   */\n  async validatePath(requestedPath) {\n    await this.ensureInitialized();\n    const expandedPath = this.#expandHome(requestedPath);\n    const absolute = path.isAbsolute(expandedPath)\n      ? path.resolve(expandedPath)\n      : this.#resolveRelativePathAgainstAllowedDirectories(expandedPath);\n\n    const normalizedRequested = this.#normalizePath(absolute);\n\n    const isAllowed = this.#isPathWithinAllowedDirectories(\n      normalizedRequested,\n      this.#allowedDirectories\n    );\n    if (!isAllowed) {\n      console.log(\n        `[validatePath] Access denied - path outside allowed directories: ${absolute} not in ${this.#allowedDirectories.join(\", \")}`\n      );\n      throw new Error(`Access denied - path outside allowed directories.`);\n    }\n\n    try {\n      const realPath = await fs.realpath(absolute);\n      const normalizedReal = this.#normalizePath(realPath);\n      if (\n        !this.#isPathWithinAllowedDirectories(\n          normalizedReal,\n          this.#allowedDirectories\n        )\n      ) {\n        console.log(\n          `[validatePath] Access denied - symlink target outside allowed directories: ${realPath} not in ${this.#allowedDirectories.join(\", \")}`\n        );\n        throw new Error(\n          `Access denied - symlink target outside allowed directories.`\n        );\n      }","sourceCodeStart":411,"sourceCodeEnd":447,"githubUrl":"https://github.com/Mintplex-Labs/anything-llm/blob/3aec848f2885144aa8f1e53b9731a04310d5d558/server/utils/agents/aibitat/plugins/filesystem/lib.js#L411-L447","documentation":"Thrown by FileOperationsLib.validatePath (server/utils/agents/aibitat/plugins/filesystem/lib.js:429) when the requested path, after tilde expansion and normalization, does not fall under any allowed directory. Allowed directories default to <STORAGE_DIR>/anythingllm-fs (or <repo>/storage/anythingllm-fs) unless the library is initialized with an explicit list. This is the sandbox boundary checked on every agent filesystem tool call (read, write, list, edit, search).","triggerScenarios":"Calling any filesystem tool with an absolute path outside the allowed roots (e.g. /tmp/notes.txt when only .../storage/anythingllm-fs is allowed); passing a relative path that does not resolve under an allowed directory; the agent reusing an absolute path from a previous container/host where STORAGE_DIR differed; changing STORAGE_DIR between runs so the default root moves.","commonSituations":"Agent tries to read /etc/passwd or ~/.ssh/id_rsa outside the sandbox; developer runs outside Docker (isToolAvailable only returns true for docker runtime or NODE_ENV=development); STORAGE_DIR env var repointed so previously valid paths now fail; user pastes a host path instead of a workspace-relative path.","solutions":["Call list_allowed_directories (getAllowedDirectories()) and rewrite the target path under one of the returned roots; relative paths resolve against the first allowed directory.","If the file legitimately lives elsewhere, copy it into the allowed root first, or initialize the FileOperationsLib with additional allowed directories.","Verify STORAGE_DIR and that ANYTHING_LLM_RUNTIME=docker (or NODE_ENV=development) so the tool and its default root exist as expected.","Check the server console log line '[validatePath] Access denied - path outside allowed directories: <abs> not in <roots>' to see both sides of the mismatch."],"exampleFix":"// before (agent tool call)\nread_file({ path: \"/tmp/notes.txt\" })   // outside sandbox -> throws\n\n// after: relative path resolves against the first allowed directory\nread_file({ path: \"notes.txt\" })\n// or explicitly:\nread_file({ path: \"/app/storage/anythingllm-fs/notes.txt\" })","handlingStrategy":"validation","validationCode":"const path = require(\"path\");\nconst os = require(\"os\");\nconst expandHome = (p) => (p.startsWith(\"~\") ? path.join(os.homedir(), p.slice(1)) : p);\nfunction isPathAllowed(fileOps, requestedPath) {\n  const abs = path.resolve(expandHome(requestedPath));\n  return fileOps.getAllowedDirectories().some(\n    (root) => abs === root || abs.startsWith(root + path.sep)\n  );\n}\nif (!isPathAllowed(fileOps, target)) {\n  target = path.join(fileOps.getAllowedDirectories()[0], path.basename(target));\n}","typeGuard":"/** @param {string} p */\nfunction isSandboxPath(fileOps, p) {\n  if (typeof p !== \"string\" || p.length === 0) return false;\n  return isPathAllowed(fileOps, p); // boolean narrow before any tool call\n}","tryCatchPattern":"try {\n  await fileOps.readFileContent(target);\n} catch (e) {\n  if (e.message.includes(\"outside allowed directories\")) {\n    // sandbox denial: re-prompt the agent with the allowed roots, never widen the sandbox silently\n    throw new Error(`Path denied. Allowed roots: ${fileOps.getAllowedDirectories().join(\", \")}`);\n  }\n  throw e;\n}","preventionTips":["Always resolve agent-supplied paths against getAllowedDirectories() before invoking a filesystem tool.","Prefer relative paths in prompts; the library resolves them under the first allowed directory.","Fix STORAGE_DIR once per deployment so the default sandbox root never moves.","Treat repeated denials for host paths as a prompt-design issue: tell the agent which root is writable."],"tags":["filesystem","sandbox","path-validation","access-denied","agent-tools"],"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"}