Fix: Path traversal vulnerability in guardrails endpoints

- Sanitize category_name in GET /guardrails/ui/category_yaml/{category_name}
  to reject path traversal sequences and validate resolved path stays within
  the expected categories directory.

- Add file path validation for presidio_ad_hoc_recognizers in the Presidio
  guardrail hook to prevent arbitrary JSON file reading via POST /guardrails.
  Rejects path traversal (..), enforces .json extension, and resolves symlinks.

Customer: Take2 Games
This commit is contained in:
shin-bot-litellm 2026-02-02 19:15:13 +00:00
parent 0a1b98895b
commit 5c03220c56
2 changed files with 54 additions and 0 deletions

View file

@ -743,6 +743,14 @@ async def get_category_yaml(category_name: str):
The raw YAML content of the category file
"""
import os
import re
# Validate category_name to prevent path traversal
# Only allow alphanumeric characters, hyphens, and underscores
if not re.match(r'^[a-zA-Z0-9_-]+$', category_name):
raise HTTPException(
status_code=400, detail="Invalid category name. Only alphanumeric characters, hyphens, and underscores are allowed."
)
# Get the categories directory path
categories_dir = os.path.join(
@ -755,6 +763,14 @@ async def get_category_yaml(category_name: str):
# Construct the file path
category_file_path = os.path.join(categories_dir, f"{category_name}.yaml")
# 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):
raise HTTPException(
status_code=400, detail="Invalid category name."
)
if not os.path.exists(category_file_path):
raise HTTPException(
status_code=404, detail=f"Category file not found: {category_name}"

View file

@ -123,6 +123,8 @@ class _OPTIONAL_PresidioPIIMasking(CustomGuardrail):
ad_hoc_recognizers = presidio_ad_hoc_recognizers
if ad_hoc_recognizers is not None:
try:
# Sanitize file path to prevent path traversal attacks
ad_hoc_recognizers = self._validate_recognizer_file_path(ad_hoc_recognizers)
with open(ad_hoc_recognizers, "r") as file:
self.ad_hoc_recognizers = json.load(file)
except FileNotFoundError:
@ -140,6 +142,42 @@ class _OPTIONAL_PresidioPIIMasking(CustomGuardrail):
presidio_anonymizer_api_base=presidio_anonymizer_api_base,
)
@staticmethod
def _validate_recognizer_file_path(file_path: str) -> str:
"""
Validate and sanitize the ad-hoc recognizers file path to prevent
path traversal attacks.
Ensures:
1. No path traversal sequences (../)
2. File must have .json extension
3. Path is resolved to absolute and checked
"""
import os
# Reject paths containing path traversal sequences
if ".." in file_path:
raise ValueError(
f"Invalid file path: path traversal sequences are not allowed. file_path={file_path}"
)
# Ensure the file has a .json extension
if not file_path.endswith(".json"):
raise ValueError(
f"Invalid file path: only .json files are allowed. file_path={file_path}"
)
# Resolve to absolute path
resolved_path = os.path.realpath(file_path)
# Ensure resolved path still ends with .json (in case of symlink tricks)
if not resolved_path.endswith(".json"):
raise ValueError(
f"Invalid file path: resolved path must be a .json file. file_path={file_path}"
)
return resolved_path
def validate_environment(
self,
presidio_analyzer_api_base: Optional[str] = None,