{"record":{"id":"21e4a589705de922","repo":"BerriAI/litellm","slug":"category-file-path-path-is-outside-the-allowed","errorCode":null,"errorMessage":"Category file path '{path}' is outside the allowed categories directory","messagePattern":"Category file path '(.+?)' is outside the allowed categories directory","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py","lineNumber":353,"sourceCode":"    @staticmethod\n    def _category_config_view(cat_config: ContentFilterCategoryConfig) -> _CategoryConfigView:\n        return {\n            \"category\": cat_config.get(\"category\"),\n            \"enabled\": cat_config.get(\"enabled\", True),\n            \"action\": cat_config.get(\"action\"),\n            \"category_file\": cat_config.get(\"category_file\"),\n        }\n\n    @staticmethod\n    def _assert_within_categories_dir(path: str, categories_dir: str) -> None:\n        \"\"\"Raise ValueError if path escapes the categories directory.\"\"\"\n        resolved: Final = os.path.realpath(path)\n        allowed: Final = os.path.realpath(categories_dir)\n        try:\n            common: Final = os.path.commonpath([resolved, allowed])\n        except ValueError:\n            # commonpath() raises ValueError on Windows when paths span different drives\n            raise ValueError(f\"Category file path '{path}' is outside the allowed categories directory\")\n        if common != allowed:\n            raise ValueError(\n                f\"Category file path '{path}' is outside the allowed categories directory '{categories_dir}'\"\n            )\n\n    def _resolve_category_file_path(self, file_path: str) -> str:\n        \"\"\"\n        Resolve a category file path that may be relative.\n\n        Paths in policy templates (e.g. category_file) are often stored as\n        relative paths like \"litellm/proxy/.../policy_templates/file.yaml\".\n        These only work when the CWD is the project root. In production\n        (Docker, installed packages, etc.) the CWD is different, so the\n        file isn't found.\n\n        Resolution order:\n        1. Return as-is if absolute or already exists (jailed to module dir).\n        2. Try joining the full path relative to this module's directory (jailed).","sourceCodeStart":335,"sourceCodeEnd":371,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py#L335-L371","documentation":"ValueError from ContentFilterGuardrail._assert_within_categories_dir raised when os.path.commonpath() itself throws — which happens on Windows when the resolved category file path and the guardrail module directory live on different drives (e.g. D:\\policies\\cat.yaml vs C:\\...\\site-packages\\litellm). Containment cannot even be computed across drives, so the jail rejects the path. Note the message omits the directory because commonpath failed before the comparison.","triggerScenarios":"Running the proxy on Windows (or a Windows CI runner) with a category_file value that resolves to an absolute path on a different drive than the litellm package directory, while LITELLM_CONTENT_FILTER_ALLOW_EXTERNAL_PATHS is not set.","commonSituations":"Windows deployments or tests that pass absolute category paths from another drive; drive-relative or cross-drive configs copied from Linux examples that used /-style paths.","solutions":["Move the category YAML files onto the same drive as the litellm installation, under the litellm_content_filter module directory.","Use a relative category_file path that resolves inside the module directory (the resolver jails those safely).","If the files must stay outside the package on a trusted host, set LITELLM_CONTENT_FILTER_ALLOW_EXTERNAL_PATHS=true to disable the directory jail.","Prefer running the proxy in the official Docker image (Linux) where cross-drive paths cannot occur."],"exampleFix":"# before (Windows, cross-drive)\npolicy_template:\n  category_file: 'D:\\policies\\pii.yaml'\n\n# after — file relocated inside the module dir, relative path\ncategory_file: 'policy_templates/pii.yaml'","handlingStrategy":"validation","validationCode":"# Startup check: a category path must be comparable with the module dir (same drive on Windows)\nimport os\n\ndef category_path_comparable(path: str, module_dir: str) -> bool:\n    try:\n        os.path.commonpath([os.path.realpath(path), os.path.realpath(module_dir)])\n        return True\n    except ValueError:\n        return False  # cross-drive (Windows) — will be rejected by the jail\n\n# if not category_path_comparable(cfg_path, os.path.dirname(litellm_content_filter.__file__)): fix or relocate","typeGuard":null,"tryCatchPattern":null,"preventionTips":["On Windows, keep category files on the same drive as the litellm package directory.","Prefer relative category_file paths that resolve inside the module dir — the resolver jails them safely.","Consider the official Linux Docker image for production to sidestep drive-letter path semantics entirely.","Reserve LITELLM_CONTENT_FILTER_ALLOW_EXTERNAL_PATHS=true for trusted hosts where the config source is controlled."],"tags":["filesystem","windows","path-validation","content-filter"],"backgroundTag":"path-outside-base-directory","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}