unslothai/unsloth · warning · ValueError

dataset path may not contain '..' segments: {raw!r}

Error message

dataset path may not contain '..' segments: {raw!r}

What it means

Traversal guard in resolve_dataset_path: after normalize_path (which converts backslashes so Windows-style '..\' can't slip through) and expanduser, the Path's parts still contain a '..' segment. '..' would let a relative dataset path escape its intended root when joined, so any occurrence is rejected outright — resolution to a root happens only after this check.

Source

Thrown at studio/backend/hub/utils/paths.py:399

    """
    try:
        path_real = os.path.normcase(os.path.realpath(str(path)))
        root_real = os.path.normcase(os.path.realpath(str(root)))
        return os.path.commonpath([path_real, root_real]) == root_real
    except (OSError, ValueError):
        return False


def resolve_dataset_path(path_value: str) -> Path:
    raw = str(path_value or "").strip()
    if "\x00" in raw:
        raise ValueError("dataset path may not contain null bytes")
    # Normalize first so Windows/UNC and backslash paths resolve like the rest of the Hub path
    # layer, and a backslashed '..' is caught by the traversal guard below.
    normalized = normalize_path(raw)
    path = Path(normalized).expanduser()
    if ".." in path.parts:
        raise ValueError(f"dataset path may not contain '..' segments: {raw!r}")
    if path.is_absolute():
        for root in (datasets_root(), dataset_uploads_root(), recipe_datasets_root()):
            try:
                _assert_contained(path, root)
                return path
            except ValueError:
                continue
        raise ValueError(f"dataset path must be relative or under a dataset root: {raw!r}")

    parts = [part for part in Path(normalized).parts if part not in ("", ".")]
    if parts[:2] == ["assets", "datasets"]:
        parts = parts[2:]
    if parts and parts[0] == "uploads":
        cleaned = Path(*parts[1:]) if len(parts) > 1 else Path()
        return dataset_uploads_root() / cleaned
    if parts and parts[0] == "recipes":
        cleaned = Path(*parts[1:]) if len(parts) > 1 else Path()
        return recipe_datasets_root() / cleaned

View on GitHub (pinned to 203007d190)

Solutions

  1. Reference the target by its own path under the dataset root, without '..' — e.g. 'shared/data.csv' if that is its logical location.
  2. If the caller has an arbitrary user path, resolve it to an absolute path first and check containment against the dataset root instead of embedding '..'.
  3. Escape/normalize user input client-side and reject '..' segments before submission.

Example fix

# before
resolve_dataset_path('uploads/../../etc/passwd')
# after
resolve_dataset_path('uploads/mydata.csv')   # stay inside the dataset root
Defensive patterns

Strategy: validation

Validate before calling

def has_dotdot(path_value: str) -> bool:
    return ".." in Path((path_value or "").strip().replace("\\", "/")).parts

Try / catch

try:
    p = resolve_dataset_path(value)
except ValueError as e:
    if "'..'" in str(e):
        return bad_request("dataset path must not contain '..' — use a path under the dataset root")

Prevention

When it happens

Trigger: Passing 'assets/datasets/../../secrets.txt', 'uploads/../config', or '..\..\windows\system32' to a dataset path resolution API; a UI file picker that returns relative paths with parent segments.

Common situations: Path-traversal attempts (../../../../etc/passwd); legitimate users trying to reference a sibling directory ('../shared/data.csv'); symlinks are fine but literal '..' text is not.

Related errors


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/d71356ecac1ffc7e. Report an issue: GitHub.