Source code for mixle.utils.paths

"""Containment for caller-supplied path segments under a declared root.

A deployment or registry name that comes from a caller is joined onto a root directory, and
``os.path.join`` / ``pathlib`` are both happy to leave that root: ``root / "tasks" / "../../escaped"``
traverses out of it, and an ABSOLUTE segment discards the root entirely
(``Path("registry") / "/tmp/x"`` is ``/tmp/x``). Nothing about that is exotic -- a name arrives from an
API request, a config file, or a CLI argument -- and the consequence is a write outside the artifact
root the caller believes they scoped it to (MXR-080-1910).

:func:`safe_segment` refuses a segment that is not a single path component. :func:`contained_path`
additionally resolves the result and re-checks it against the resolved root, which is what catches a
pre-existing symlink inside the root pointing elsewhere -- a check on the string alone cannot see
that. ``mixle.inference.production.registry`` has enforced the same two rules on its own store since
MXR-080-0264; this is that logic made shared rather than re-derived.
"""

from __future__ import annotations

import os
from pathlib import Path

__all__ = ["contained_path", "safe_segment"]


[docs] def safe_segment(segment: str, *, kind: str = "name") -> str: """Return ``segment`` unchanged, or raise unless it is a single safe path component.""" if not isinstance(segment, str) or not segment.strip(): raise ValueError(f"{kind} must be a non-empty string, got {segment!r}") if ( segment in (os.curdir, os.pardir) or os.sep in segment or (os.altsep and os.altsep in segment) or "\x00" in segment or os.path.isabs(segment) or os.path.basename(segment) != segment ): raise ValueError( f"unsafe {kind} {segment!r}: must be a single path component -- no separators, no '..', " "and not absolute. Joining it onto a root would write outside that root." ) return segment
[docs] def contained_path(root: str | os.PathLike[str], *segments: str, kind: str = "name") -> Path: """Join ``segments`` under ``root``, refusing anything that escapes it. Each segment is checked as a single component, and the resolved result is then re-checked against the resolved root so a symlink already inside the root cannot redirect the write outside it. """ base = Path(root) for segment in segments: safe_segment(segment, kind=kind) candidate = base.joinpath(*segments) root_real = os.path.realpath(base) candidate_real = os.path.realpath(candidate) if candidate_real != root_real and not candidate_real.startswith(root_real + os.sep): raise ValueError( f"unsafe {kind}: {'/'.join(segments)!r} resolves to {candidate_real!r}, which is outside " f"the declared root {root_real!r}. A symlink inside the root can redirect a write that " "looks contained by its string alone." ) return candidate