Safe Path Join Against Directory Traversal
Resolve user-supplied relative paths under a fixed root, reject absolute or traversal inputs, and verify the final path stays inside.
Code
from pathlib import Path
class UnsafePathError(ValueError):
pass
def safe_join(root: str | Path, user_path: str) -> Path:
if not isinstance(user_path, str) or not user_path.strip():
raise UnsafePathError("path must be a non-empty string")
if "\x00" in user_path:
raise UnsafePathError("path must not contain NUL bytes")
candidate = Path(user_path)
if candidate.is_absolute() or candidate.drive:
raise UnsafePathError("absolute paths are not allowed")
base = Path(root).resolve()
target = (base / candidate).resolve()
try:
target.relative_to(base)
except ValueError as error:
raise UnsafePathError(f"path escapes root: {user_path!r}") from error
return target
def main() -> None:
root = Path.cwd() / "public"
for value in ["reports/2026/q1.csv", "../secrets.env", "/etc/passwd", "a/../../b"]:
try:
print(value, "->", safe_join(root, value))
except UnsafePathError as error:
print(value, "-> rejected:", error)
if __name__ == "__main__":
main()
Report a problem with this exampleOpens a pre-filled issue on GitHub.