Mintplex-Labs/anything-llm · error · Error

Access denied - path outside allowed directories.

Error message

Access denied - path outside allowed directories.

What it means

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).

Solutions

  1. Call list_allowed_directories (getAllowedDirectories()) and rewrite the target path under one of the returned roots; relative paths resolve against the first allowed directory.
  2. If the file legitimately lives elsewhere, copy it into the allowed root first, or initialize the FileOperationsLib with additional allowed directories.
  3. Verify STORAGE_DIR and that ANYTHING_LLM_RUNTIME=docker (or NODE_ENV=development) so the tool and its default root exist as expected.
  4. Check the server console log line '[validatePath] Access denied - path outside allowed directories: <abs> not in <roots>' to see both sides of the mismatch.

Example fix

// before (agent tool call)
read_file({ path: "/tmp/notes.txt" })   // outside sandbox -> throws

// after: relative path resolves against the first allowed directory
read_file({ path: "notes.txt" })
// or explicitly:
read_file({ path: "/app/storage/anythingllm-fs/notes.txt" })
Defensive patterns

Strategy: validation

Validate before calling

const path = require("path");
const os = require("os");
const expandHome = (p) => (p.startsWith("~") ? path.join(os.homedir(), p.slice(1)) : p);
function isPathAllowed(fileOps, requestedPath) {
  const abs = path.resolve(expandHome(requestedPath));
  return fileOps.getAllowedDirectories().some(
    (root) => abs === root || abs.startsWith(root + path.sep)
  );
}
if (!isPathAllowed(fileOps, target)) {
  target = path.join(fileOps.getAllowedDirectories()[0], path.basename(target));
}

Type guard

/** @param {string} p */
function isSandboxPath(fileOps, p) {
  if (typeof p !== "string" || p.length === 0) return false;
  return isPathAllowed(fileOps, p); // boolean narrow before any tool call
}

Try / catch

try {
  await fileOps.readFileContent(target);
} catch (e) {
  if (e.message.includes("outside allowed directories")) {
    // sandbox denial: re-prompt the agent with the allowed roots, never widen the sandbox silently
    throw new Error(`Path denied. Allowed roots: ${fileOps.getAllowedDirectories().join(", ")}`);
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of Mintplex-Labs/anything-llm@3aec848f28 (2026-08-18). Data as JSON: /api/errors/19b2395317182214. Report an issue: GitHub.

Appendix: source

Thrown at server/utils/agents/aibitat/plugins/filesystem/lib.js:429

   */
  async validatePath(requestedPath) {
    await this.ensureInitialized();
    const expandedPath = this.#expandHome(requestedPath);
    const absolute = path.isAbsolute(expandedPath)
      ? path.resolve(expandedPath)
      : this.#resolveRelativePathAgainstAllowedDirectories(expandedPath);

    const normalizedRequested = this.#normalizePath(absolute);

    const isAllowed = this.#isPathWithinAllowedDirectories(
      normalizedRequested,
      this.#allowedDirectories
    );
    if (!isAllowed) {
      console.log(
        `[validatePath] Access denied - path outside allowed directories: ${absolute} not in ${this.#allowedDirectories.join(", ")}`
      );
      throw new Error(`Access denied - path outside allowed directories.`);
    }

    try {
      const realPath = await fs.realpath(absolute);
      const normalizedReal = this.#normalizePath(realPath);
      if (
        !this.#isPathWithinAllowedDirectories(
          normalizedReal,
          this.#allowedDirectories
        )
      ) {
        console.log(
          `[validatePath] Access denied - symlink target outside allowed directories: ${realPath} not in ${this.#allowedDirectories.join(", ")}`
        );
        throw new Error(
          `Access denied - symlink target outside allowed directories.`
        );
      }

View on GitHub (pinned to 3aec848f28)