From 737595729b55da111eed281e2437cf4f810c2a41 Mon Sep 17 00:00:00 2001 From: shin-bot-litellm Date: Sat, 7 Feb 2026 08:45:41 +0000 Subject: [PATCH] fix(security): address Greptile review comments for path traversal protection - guardrail_endpoints.py: Fix edge case where file named exactly in the categories directory could be rejected. Added 'or resolved_path == resolved_categories_dir' to the boundary check. - presidio.py: Add directory boundary check to _validate_recognizer_file_path to prevent absolute path attacks. The resolved path must now stay within the current working directory, similar to the guardrail_endpoints.py pattern. These changes address the security review comments from Greptile on PR #20286. --- litellm/proxy/guardrails/guardrail_endpoints.py | 2 +- litellm/proxy/guardrails/guardrail_hooks/presidio.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) 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(