mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
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:
parent
0a1b98895b
commit
5c03220c56
2 changed files with 54 additions and 0 deletions
|
|
@ -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}"
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue