diff --git a/litellm/proxy/guardrails/guardrail_endpoints.py b/litellm/proxy/guardrails/guardrail_endpoints.py index 1b240d6eeff..0dab6cfdecf 100644 --- a/litellm/proxy/guardrails/guardrail_endpoints.py +++ b/litellm/proxy/guardrails/guardrail_endpoints.py @@ -766,7 +766,7 @@ async def get_category_yaml(category_name: str): # Resolve to absolute path and verify it stays within categories_dir resolved_path = os.path.realpath(category_file_path) resolved_categories_dir = os.path.realpath(categories_dir) - if not resolved_path.startswith(resolved_categories_dir + os.sep): + if not (resolved_path.startswith(resolved_categories_dir + os.sep) or resolved_path == resolved_categories_dir): raise HTTPException( status_code=400, detail="Invalid category name." ) diff --git a/litellm/proxy/guardrails/guardrail_hooks/presidio.py b/litellm/proxy/guardrails/guardrail_hooks/presidio.py index dce99b571b1..07d52b1c302 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/presidio.py +++ b/litellm/proxy/guardrails/guardrail_hooks/presidio.py @@ -152,6 +152,7 @@ class _OPTIONAL_PresidioPIIMasking(CustomGuardrail): 1. No path traversal sequences (../) 2. File must have .json extension 3. Path is resolved to absolute and checked + 4. Path must stay within the current working directory (directory boundary check) """ import os @@ -176,6 +177,14 @@ class _OPTIONAL_PresidioPIIMasking(CustomGuardrail): f"Invalid file path: resolved path must be a .json file. file_path={file_path}" ) + # Directory boundary check: ensure the resolved path stays within the + # current working directory to prevent absolute path attacks + base_dir = os.path.realpath(os.getcwd()) + if not (resolved_path.startswith(base_dir + os.sep) or resolved_path == base_dir): + raise ValueError( + f"Invalid file path: path must be within the working directory. file_path={file_path}" + ) + return resolved_path def validate_environment(