mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-08 22:21:35 +00:00
sanitize S3 log key filename to prevent 403 for s3:// batch file IDs
This commit is contained in:
parent
d7c419bfee
commit
5870052a2d
2 changed files with 54 additions and 1 deletions
|
|
@ -185,12 +185,36 @@ def get_s3_object_key(
|
|||
start_time: datetime,
|
||||
s3_file_name: str,
|
||||
) -> str:
|
||||
# `s3_file_name` sometimes comes from an upstream "id" that can be an S3 URI
|
||||
# (e.g. `s3://bucket/some/key.jsonl`). If we include `/` directly in the
|
||||
# object key filename component, it creates extra path segments and can
|
||||
# break S3 bucket/prefix IAM policies.
|
||||
raw_file_name = "" if s3_file_name is None else str(s3_file_name)
|
||||
import hashlib
|
||||
import re
|
||||
|
||||
# Replace any characters outside the safe filename set.
|
||||
# This also converts `/` and `:` into `_`, preventing accidental directory
|
||||
# traversal / extra IAM prefix segments.
|
||||
safe_file_name = re.sub(r"[^A-Za-z0-9._-]+", "_", raw_file_name)
|
||||
safe_file_name = safe_file_name.strip("_")
|
||||
|
||||
if not safe_file_name:
|
||||
# Fall back to a deterministic value to avoid empty keys.
|
||||
safe_file_name = "unknown"
|
||||
|
||||
# Keep keys reasonably small even if `id` is a long URI.
|
||||
max_len = 180
|
||||
if len(safe_file_name) > max_len:
|
||||
suffix_hash = hashlib.sha256(raw_file_name.encode("utf-8")).hexdigest()[:12]
|
||||
safe_file_name = f"{safe_file_name[: max_len - 13]}_{suffix_hash}"
|
||||
|
||||
s3_object_key = (
|
||||
(s3_path.rstrip("/") + "/" if s3_path else "")
|
||||
+ prefix
|
||||
+ start_time.strftime("%Y-%m-%d")
|
||||
+ "/"
|
||||
+ s3_file_name
|
||||
+ safe_file_name
|
||||
) # we need the s3 key to include the time, so we log cache hits too
|
||||
s3_object_key += ".json"
|
||||
return s3_object_key
|
||||
|
|
|
|||
29
tests/test_litellm/integrations/test_s3_object_key.py
Normal file
29
tests/test_litellm/integrations/test_s3_object_key.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
from datetime import datetime, timezone
|
||||
|
||||
from litellm.integrations.s3 import get_s3_object_key
|
||||
|
||||
|
||||
def test_get_s3_object_key_sanitizes_embedded_s3_uri_in_filename():
|
||||
start_time = datetime(2026, 3, 9, 17, 40, 11, 901585, tzinfo=timezone.utc)
|
||||
s3_path = "LiteLLMAPPLogs"
|
||||
prefix = ""
|
||||
s3_file_name = (
|
||||
"time-17-40-11-901585_s3://bucket-int/litellm-bedrock-files-us.anthropic."
|
||||
"claude-sonnet-4-5-20250929-v1-0-29ea93-452e-8a2f.jsonl.json"
|
||||
)
|
||||
|
||||
s3_object_key = get_s3_object_key(
|
||||
s3_path=s3_path,
|
||||
prefix=prefix,
|
||||
start_time=start_time,
|
||||
s3_file_name=s3_file_name,
|
||||
)
|
||||
|
||||
assert s3_object_key.startswith("LiteLLMAPPLogs/2026-03-09/")
|
||||
assert s3_object_key.endswith(".json")
|
||||
|
||||
# The only path separators allowed are the ones we generate for prefix/date.
|
||||
file_component = s3_object_key[len("LiteLLMAPPLogs/2026-03-09/") :]
|
||||
assert "/" not in file_component
|
||||
assert "s3://" not in s3_object_key
|
||||
|
||||
Loading…
Add table
Reference in a new issue