{"record":{"id":"e30122d08540509d","repo":"crewAIInc/crewAI","slug":"path-format-path-for-display-resolved-path-reso","errorCode":null,"errorMessage":"Path '{format_path_for_display(resolved_path, resolved_base)}' is outside the allowed directory. Set {_UNSAFE_PATHS_ENV}=true to bypass this check.","messagePattern":"Path '(.+?)' is outside the allowed directory\\. Set (.+?)=true to bypass this check\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"lib/crewai-tools/src/crewai_tools/security/safe_path.py","lineNumber":115,"sourceCode":"            path,\n        )\n        return os.path.realpath(path)\n\n    if base_dir is None:\n        base_dir = os.getcwd()\n\n    resolved_base = os.path.realpath(base_dir)\n    resolved_path = os.path.realpath(\n        os.path.join(resolved_base, path) if not os.path.isabs(path) else path\n    )\n\n    # Ensure the resolved path is within the base directory.\n    # When resolved_base already ends with a separator (e.g. the filesystem\n    # root \"/\"), appending os.sep would double it (\"//\"), so use the base\n    # as-is in that case.\n    prefix = resolved_base if resolved_base.endswith(os.sep) else resolved_base + os.sep\n    if not resolved_path.startswith(prefix) and resolved_path != resolved_base:\n        raise ValueError(\n            f\"Path '{format_path_for_display(resolved_path, resolved_base)}' is \"\n            f\"outside the allowed directory. \"\n            f\"Set {_UNSAFE_PATHS_ENV}=true to bypass this check.\"\n        )\n\n    return resolved_path\n\n\ndef validate_directory_path(path: str, base_dir: str | None = None) -> str:\n    \"\"\"Validate that a directory path is safe to read.\n\n    Same as :func:`validate_file_path` but also checks that the path\n    is an existing directory.\n\n    Args:\n        path: The directory path to validate.\n        base_dir: Allowed root directory. Defaults to ``os.getcwd()``.\n","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/crewAIInc/crewAI/blob/754d7323beb2fd042e33444a115ea2d5a47193f0/lib/crewai-tools/src/crewai_tools/security/safe_path.py#L97-L133","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass paths relative to the allowed base directory and let the validator resolve them — avoid absolute paths unless they are genuinely under the base.","If legitimate files live outside the default base (cwd), pass an explicit base_dir covering them: validate_file_path(path, base_dir='/srv/data').","Check for symlinks in the path: realpath exposes where they actually point; move the real file inside the base or extend base_dir.","Only as a last resort in trusted local dev, set the documented env var (_UNSAFE_PATHS_ENV) to true to bypass — never in production."],"exampleFix":"# before\nvalidated = validate_file_path(\"../../secrets/key.pem\")  # escapes base\n\n# after\nvalidated = validate_file_path(\"secrets/key.pem\", base_dir=\"/srv/app\")\n# or move the file under the allowed base and use its relative path","handlingStrategy":"validation","validationCode":"import os\n\ndef ensure_within_base(path: str, base_dir: str) -> str | None:\n    base = os.path.realpath(base_dir)\n    resolved = os.path.realpath(path if os.path.isabs(path) else os.path.join(base, path))\n    prefix = base if base.endswith(os.sep) else base + os.sep\n    return resolved if resolved == base or resolved.startswith(prefix) else None","typeGuard":null,"tryCatchPattern":"try:\n    validated = validate_file_path(user_path, base_dir=BASE)\nexcept ValueError as e:\n    if \"outside the allowed directory\" in str(e):\n        reject(user_path)  # treat as unsafe input, do not retry\n    raise","preventionTips":["Never pass raw user/LLM paths to loaders; resolve and contain them against an explicit base first.","Prefer relative paths plus an explicit base_dir over absolute paths.","Remember realpath resolves symlinks — audit any symlink inside the base that points outside.","Keep the unsafe-paths env hatch disabled in production."],"tags":["security","path-traversal","filesystem","validation"],"backgroundTag":null,"analyzedSha":"754d7323beb2fd042e33444a115ea2d5a47193f0","analyzedAt":"2026-08-15T04:06:56.746Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}