sanitize S3 log key filename to prevent 403 for s3:// batch file IDs

This commit is contained in:
naaa760 2026-03-20 15:15:08 +05:30
parent d7c419bfee
commit 5870052a2d
2 changed files with 54 additions and 1 deletions

View file

@ -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

View 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