Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/common_utils/path_utils.py: 50%

18 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-10 12:01 +0000

1""" 

2Safe filesystem path construction for user-controlled inputs. 

3 

4Use safe_join() instead of os.path.join() whenever a path component 

5comes from user input (request parameters, uploaded filenames, etc.) 

6to prevent directory traversal attacks. 

7""" 

8 

9import os 

10from typing import Final 

11 

12 

13def safe_join(base_dir: str, *parts: str) -> str: 

14 """ 

15 Join path components and verify the result stays within base_dir. 

16 

17 Resolves symlinks and '..' sequences, then checks the final path 

18 is a descendant of base_dir. Raises ValueError if traversal is 

19 detected. 

20 

21 Args: 

22 base_dir: The trusted base directory. 

23 *parts: User-controlled path components to append. 

24 

25 Returns: 

26 The resolved absolute path as a string. 

27 

28 Raises: 

29 ValueError: If the resolved path escapes base_dir. 

30 """ 

31 for part in parts: 

32 if "\x00" in part: 32 ↛ 33line 32 didn't jump to line 33 because the condition on line 32 was never true

33 raise ValueError("Path contains null byte") 

34 base: Final = os.path.realpath(base_dir) 

35 resolved: Final = os.path.realpath(os.path.join(base, *parts)) 

36 if not (resolved.startswith(base + os.sep) or resolved == base): 36 ↛ 37line 36 didn't jump to line 37 because the condition on line 36 was never true

37 raise ValueError(f"Path {resolved!r} escapes base directory {base!r}") 

38 return resolved 

39 

40 

41def safe_filename(filename: str) -> str: 

42 """ 

43 Extract a safe filename from a user-supplied path. 

44 

45 Strips all directory components (both Unix and Windows separators), 

46 returning only the final name. Use this for uploaded file names 

47 before writing to disk. 

48 

49 Args: 

50 filename: User-supplied filename (may contain path separators). 

51 

52 Returns: 

53 The basename only, with no directory components. 

54 

55 Raises: 

56 ValueError: If the resulting filename is empty or contains null bytes. 

57 """ 

58 if "\x00" in filename: 

59 raise ValueError("Filename contains null byte") 

60 # Normalize backslash separators for cross-platform safety 

61 name: Final = filename.replace("\\", "/").rsplit("/", 1)[-1] 

62 if not name or name in (".", ".."): 

63 raise ValueError("Empty or unsafe filename") 

64 return name