{"record":{"id":"a8930fd9eb16968f","repo":"crewAIInc/crewAI","slug":"path-validated-is-not-a-directory","errorCode":null,"errorMessage":"Path '{validated}' is not a directory.","messagePattern":"Path '(.+?)' is not a directory\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"lib/crewai-tools/src/crewai_tools/security/safe_path.py","lineNumber":142,"sourceCode":"def 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\n    Returns:\n        The resolved, validated absolute path.\n\n    Raises:\n        ValueError: If the path escapes the allowed directory or is not a directory.\n    \"\"\"\n    validated = validate_file_path(path, base_dir)\n    if not os.path.isdir(validated):\n        raise ValueError(f\"Path '{validated}' is not a directory.\")\n    return validated\n\n\n# Private and reserved IP ranges that should not be accessed\n_BLOCKED_IPV4_NETWORKS = [\n    ipaddress.ip_network(\"10.0.0.0/8\"),\n    ipaddress.ip_network(\"172.16.0.0/12\"),\n    ipaddress.ip_network(\"192.168.0.0/16\"),\n    ipaddress.ip_network(\"127.0.0.0/8\"),\n    ipaddress.ip_network(\"169.254.0.0/16\"),  # Link-local / cloud metadata\n    ipaddress.ip_network(\"0.0.0.0/32\"),\n]\n\n_BLOCKED_IPV6_NETWORKS = [\n    ipaddress.ip_network(\"::1/128\"),\n    ipaddress.ip_network(\"::/128\"),\n    ipaddress.ip_network(\"fc00::/7\"),  # Unique local addresses\n    ipaddress.ip_network(\"fe80::/10\"),  # Link-local IPv6","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/crewAIInc/crewAI/blob/754d7323beb2fd042e33444a115ea2d5a47193f0/lib/crewai-tools/src/crewai_tools/security/safe_path.py#L124-L160","documentation":"Raised by validate_directory_path after validate_file_path succeeds but os.path.isdir() fails: the path is inside the allowed base, yet it is not a directory (it is a file, a symlink to a file, or does not exist at all). The check follows symlinks, so a symlink to a directory passes while a dangling one fails.","triggerScenarios":"Passing a file path where a directory is expected (e.g. pointing a recursive loader's directory argument at a single document); passing a path that does not exist yet (checked before creation); a dangling symlink; race conditions where the directory is deleted between resolution and the isdir call.","commonSituations":"Config keys like data_dir/docs_path given a filename by mistake; scripts run before the directory is created; deploy pipelines where the volume is mounted at a different path than configured.","solutions":["Verify with os.path.isdir(path) before calling and print the resolved realpath to confirm what you are actually pointing at.","Create the directory first if it is expected to exist: os.makedirs(path, exist_ok=True) (also resolves the not-yet-created case).","Check the config value — a file path in a directory-valued setting is the most common cause.","Ensure the mount/volume is attached before the process starts in containerized deployments."],"exampleFix":"# before\nvalidated = validate_directory_path(\"/srv/data/report.pdf\")  # it's a file\n\n# after\nimport os\npath = \"/srv/data/reports\"\nos.makedirs(path, exist_ok=True)\nvalidated = validate_directory_path(path)","handlingStrategy":"validation","validationCode":"import os\n\ndef ensure_directory(path: str, create: bool = False) -> bool:\n    if create and not os.path.exists(path):\n        os.makedirs(path, exist_ok=True)\n    return os.path.isdir(os.path.realpath(path))","typeGuard":null,"tryCatchPattern":"try:\n    validated = validate_directory_path(dir_path)\nexcept ValueError as e:\n    if \"is not a directory\" in str(e):\n        raise ConfigError(f\"expected a directory, got file/missing path: {dir_path}\") from e\n    raise","preventionTips":["Type-check config values: directory settings must end in a directory, not a file.","Create required directories at startup with os.makedirs(..., exist_ok=True).","Validate mounts exist before process start in containerized deployments."],"tags":["filesystem","validation","path","configuration"],"backgroundTag":null,"analyzedSha":"754d7323beb2fd042e33444a115ea2d5a47193f0","analyzedAt":"2026-08-15T04:06:56.746Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}