Mintplex-Labs/anything-llm · error · Error

Access denied - symlink target outside allowed directories.

Error message

Access denied - symlink target outside allowed directories.

What it means

Thrown by validatePath when fs.realpath() resolves the requested path (via symlink) to a physical location outside the allowed directories. Even though the requested path string sits inside the sandbox, the operating system would actually read/write the link target, so the library rejects it. This blocks symlink-escape attacks against the agent filesystem sandbox.

Solutions

  1. Remove or replace the symlink with a real copy of the target inside the allowed directory.
  2. If the target location must be accessible, initialize the filesystem library with the real target directory added to the allowed list.
  3. Inspect with ls -la / readlink -f <path> to confirm which link escapes, then fix that specific link.

Example fix

# before
ln -s /var/reports /app/storage/anythingllm-fs/reports
read_file({ path: "reports/q3.txt" })  # throws: symlink target outside

# after
cp -r /var/reports /app/storage/anythingllm-fs/reports
read_file({ path: "reports/q3.txt" })  # ok
Defensive patterns

Strategy: try-catch

Validate before calling

const fs = require("fs").promises;
const path = require("path");
async function assertNoSymlinkEscape(fileOps, target) {
  const allowed = fileOps.getAllowedDirectories();
  const real = await fs.realpath(target).catch(() => null);
  if (real && !allowed.some((r) => real === r || real.startsWith(r + path.sep))) {
    throw new Error(`Refusing ${target}: symlink resolves to ${real}, outside sandbox`);
  }
}

Try / catch

try {
  const validated = await fileOps.validatePath(p);
} catch (e) {
  if (e.message.includes("symlink target outside allowed directories")) {
    // security event: log path + realpath, reject; do not auto-retry or rewrite the path
    securityLog(`symlink escape attempt: ${p}`);
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: A file inside <storage>/anythingllm-fs is a symlink to /etc, /root, or any directory outside the allowed roots; agent writes to a symlinked path whose target is on another mount; a previously-clean path becomes a symlink after a package or setup step creates links in the workspace.

Common situations: User symlinks a folder into the sandbox to 'share' documents with the agent; ln -s /var/data ./storage/anythingllm-fs/data in a Docker volume setup; CI copies a tree that contains absolute symlinks that only resolve on the build host.

Understand the failure class

Related errors


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

Appendix: source

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

      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.`
        );
      }
      return realPath;
    } catch (error) {
      if (error.code === "ENOENT") {
        const parentDir = path.dirname(absolute);
        try {
          const realParentPath = await fs.realpath(parentDir);
          const normalizedParent = this.#normalizePath(realParentPath);
          if (
            !this.#isPathWithinAllowedDirectories(
              normalizedParent,
              this.#allowedDirectories
            )
          ) {
            console.log(
              `[validatePath] Access denied - parent directory outside allowed directories: ${realParentPath} not in ${this.#allowedDirectories.join(", ")}`

View on GitHub (pinned to 3aec848f28)