ComfyUI/comfy/security/path_validator.py

55 lines
1.8 KiB
Python

"""Centralized path-traversal validation for HTTP-exposed file paths.
Replaces ad-hoc checks (`'..' in filename`, `commonpath` comparisons, etc.)
that previously lived inline across server.py upload/view endpoints.
Issue: https://github.com/Comfy-Org/ComfyUI/issues/13742
"""
from __future__ import annotations
import os
from pathlib import Path
from typing import Union
PathLike = Union[str, os.PathLike]
def resolve_safe_path(base_dir: PathLike, *user_parts: PathLike) -> Path | None:
"""Resolve user-supplied path parts against a trusted base directory.
Joins ``user_parts`` to ``base_dir``, fully resolves the result (following
symlinks), and returns it only if it stays inside ``base_dir``. Returns
``None`` for any unsafe input — null bytes, absolute user parts that escape
the base, ``..`` segments that walk above the base, symlinks that point
outside the base, or any OS-level resolution failure.
Callers must use the returned ``Path`` for filesystem operations and must
not re-join the original user input afterwards, or the validation is moot.
"""
for part in user_parts:
if _has_unsafe_chars(part):
return None
try:
base = Path(base_dir).resolve(strict=False)
except (OSError, ValueError):
return None
try:
joined = base.joinpath(*(os.fspath(p) for p in user_parts))
candidate = joined.resolve(strict=False)
except (OSError, ValueError):
return None
try:
candidate.relative_to(base)
except ValueError:
return None
return candidate
def _has_unsafe_chars(part: PathLike) -> bool:
s = os.fspath(part)
# NUL byte: some platforms truncate paths at \x00, which can defeat
# subsequent containment checks performed on the truncated string.
return "\x00" in s