crewAIInc/crewAI · error · ValueError

Path ' ' is outside the allowed directory. Set =true to…

Error message

Path '{format_path_for_display(resolved_path, resolved_base)}' is outside the allowed directory. Set {_UNSAFE_PATHS_ENV}=true to bypass this check.

What it means

Security guard in crewai_tools.security.safe_path: validate_file_path resolves both the path and the allowed base_dir with os.path.realpath (which follows symlinks and strips ..), then requires the result to sit under the base. Any escape — via ../, absolute paths pointing elsewhere, or symlinks that resolve outside — raises this ValueError. The message names the offending resolved path and the env var escape hatch.

Solutions

  1. Pass paths relative to the allowed base directory and let the validator resolve them — avoid absolute paths unless they are genuinely under the base.
  2. If legitimate files live outside the default base (cwd), pass an explicit base_dir covering them: validate_file_path(path, base_dir='/srv/data').
  3. Check for symlinks in the path: realpath exposes where they actually point; move the real file inside the base or extend base_dir.
  4. Only as a last resort in trusted local dev, set the documented env var (_UNSAFE_PATHS_ENV) to true to bypass — never in production.

Example fix

# before
validated = validate_file_path("../../secrets/key.pem")  # escapes base

# after
validated = validate_file_path("secrets/key.pem", base_dir="/srv/app")
# or move the file under the allowed base and use its relative path
Defensive patterns

Strategy: validation

Validate before calling

import os

def ensure_within_base(path: str, base_dir: str) -> str | None:
    base = os.path.realpath(base_dir)
    resolved = os.path.realpath(path if os.path.isabs(path) else os.path.join(base, path))
    prefix = base if base.endswith(os.sep) else base + os.sep
    return resolved if resolved == base or resolved.startswith(prefix) else None

Try / catch

try:
    validated = validate_file_path(user_path, base_dir=BASE)
except ValueError as e:
    if "outside the allowed directory" in str(e):
        reject(user_path)  # treat as unsafe input, do not retry
    raise

Prevention

When it happens

Trigger: Passing '../../etc/passwd' as a file source; passing an absolute path outside base_dir (default os.getcwd()); a symlink inside the base that points to a file outside it; cases where the base itself is a symlinked path and the caller compares against the un-resolved variant. The env var named by _UNSAFE_PATHS_ENV disables the check entirely when set to true.

Common situations: Agent tools accepting LLM-supplied or user-supplied paths that traverse out of the workspace; deployments where the code directory is a symlink (e.g. /app -> /var/app) and realpath resolves differently than expected; CI running from a different realpath than the configured base.

Related errors


AI-assisted analysis of crewAIInc/crewAI@754d7323be (2026-08-15). Data as JSON: /api/errors/e30122d08540509d. Report an issue: GitHub.

Appendix: source

Thrown at lib/crewai-tools/src/crewai_tools/security/safe_path.py:115

            path,
        )
        return os.path.realpath(path)

    if base_dir is None:
        base_dir = os.getcwd()

    resolved_base = os.path.realpath(base_dir)
    resolved_path = os.path.realpath(
        os.path.join(resolved_base, path) if not os.path.isabs(path) else path
    )

    # Ensure the resolved path is within the base directory.
    # When resolved_base already ends with a separator (e.g. the filesystem
    # root "/"), appending os.sep would double it ("//"), so use the base
    # as-is in that case.
    prefix = resolved_base if resolved_base.endswith(os.sep) else resolved_base + os.sep
    if not resolved_path.startswith(prefix) and resolved_path != resolved_base:
        raise ValueError(
            f"Path '{format_path_for_display(resolved_path, resolved_base)}' is "
            f"outside the allowed directory. "
            f"Set {_UNSAFE_PATHS_ENV}=true to bypass this check."
        )

    return resolved_path


def validate_directory_path(path: str, base_dir: str | None = None) -> str:
    """Validate that a directory path is safe to read.

    Same as :func:`validate_file_path` but also checks that the path
    is an existing directory.

    Args:
        path: The directory path to validate.
        base_dir: Allowed root directory. Defaults to ``os.getcwd()``.

View on GitHub (pinned to 754d7323be)