Pathlib in Python: Modern, Secure File Path Handling
- When building Python applications, working with files and directories is unavoidable.
- Traditionally, developers relied on plain strings to represent file paths, concatenating them with operators like + or using functions like os.path.join.
But here’s the problem: strings don’t understand paths. To a string, ../secrets.txt is just a sequence of characters — not a potential security risk.
🔑Enter pathlib: Python’s modern, object-oriented library for working with filesystem paths.🔑
🔹 Why Pathlib?
- Object-oriented paths: A path is no longer a fragile string but a Path object with methods to manipulate and validate safely.
- Cross-platform safety: Windows uses backslashes (\), Linux/macOS use forward slashes (/). pathlib automatically handles this.
- Readable code: Cleaner and more intuitive APIs compared to string concatenation.
- Security benefits: Provides tools to resolve, normalize, and restrict file access — reducing vulnerabilities like Path Traversal.
🔹 The Problem with Strings
Let’s look at a vulnerable example using plain strings:
import os
BASE_DIR = "/srv/reports"
def get_report(filename):
full_path = os.path.join(BASE_DIR, filename) # unsafe join
with open(full_path, "r") as f:
return f.read()
# Attacker input:
print(get_report("../../etc/passwd"))
# 🔥 Reads sensitive file outside BASE_DIRIf an attacker passes ../../etc/passwd as filename, os.path.join happily constructs /srv/reports/../../etc/passwd.
After resolution, this points to /etc/passwd — a sensitive system file.
🚨 This is a classic Path Traversal vulnerability.🚨
🔹 Pathlib to the Rescue
- Now let’s see the same code with pathlib:
from pathlib import Path
BASE_DIR = Path("/srv/reports").resolve()
def get_report(filename):
requested_path = (BASE_DIR / filename).resolve()
# Security check: ensure path is still inside BASE_DIR
if not str(requested_path).startswith(str(BASE_DIR)):
raise ValueError("Invalid file request")
return requested_path.read_text()
# Attacker input blocked:
print(get_report("../../etc/passwd"))
# ❌ Raises ValueError instead of reading system file✅ Why is this safer?
➡️BASE_DIR = Path(“/srv/reports”).resolve()
- Converts the directory into a Path object.
- .resolve() normalizes the path, removing . and …
➡️(BASE_DIR / filename).resolve()
- Uses / operator (overloaded for paths) to join safely.
- .resolve() collapses malicious inputs like ../../etc/passwd into their true location.
➡️Security check ensures the resolved file path stays within BASE_DIR.
Get Siddiquimohammad’s stories in your inbox
Join Medium for free to get updates from this writer.
🔹 Key Security Features of Pathlib
- .resolve() → Expands symlinks, removes traversal tricks.
- .is_absolute() → Detects if a path is trying to escape with an absolute location.
- .parts → Lets you inspect each component of the path (helpful for spotting ..).
- Readable methods → e.g., .suffix for file extension, .name for filename only.
Example:
from pathlib import Path
# Create a Path object pointing to a suspicious file
p = Path("../etc/passwd")
# Break down the path into parts
print("Parts:", p.parts)
# Output: ('..', 'etc', 'passwd')
# Check if it's an absolute path
print("Is absolute?:", p.is_absolute())
# Output: False
# Get the file name only
print("Name:", p.name)
# Output: 'passwd'
# Get the file extension (suffix)
print("Suffix:", p.suffix)
# Output: '' (empty, since 'passwd' has no extension)
# Normalize the path (resolve removes '..')
print("Resolved:", p.resolve())
# Example Output: '/home/user/etc/passwd'🔹 Secure Coding Best Practices with Pathlib
- Always resolve paths before use
safe_path = Path(user_input).resolve()- Enforce a root directory lock-in
if not str(safe_path).startswith(str(BASE_DIR)):
raise ValueError(“Unauthorized path”) - Whitelist file extensions
if safe_path.suffix.lower() not in {".txt", ".log"}:
raise ValueError("Disallowed file type")3. Never trust raw user input in file operations
🔹 Why This Matters for Secure Python Coding
- Many security vulnerabilities arise from treating user input as trusted strings.
- With paths, attackers abuse this by injecting traversal sequences (../, %2e%2e/) to escape directories and access sensitive files.
By using pathlib, you:
- Force your application to think in paths, not strings.
- Gain built-in normalization, making it harder for attackers to sneak past.
- Make your code cleaner, more Pythonic, and secure.
🔹 Final Thoughts
- In modern Python, pathlib should be your default choice for file path handling.
- It’s not just about developer convenience — it’s about writing secure, maintainable, and cross-platform code.
If you’re building web apps, microservices, or scripts that process files:
👉 Replace os.path + string concatenation with pathlib.Path.
👉 Always resolve and validate user input.
This small shift can prevent one of the oldest yet still critical vulnerabilities: Path Traversal.









