unslothai/unsloth · error · ValueError

save_directory may not contain '..' segments

Error message

save_directory may not contain '..' segments

What it means

ValueError from _validate_save_directory when the path contains '..' segments in any interpretation — POSIX parts, Windows PureWindowsPath parts, or naive backslash/slash splitting. This blocks directory traversal: export destinations must be explicit paths, not relative escapes, and the redundant multi-view check closes Windows/POSIX mismatch loopholes (e.g. '..\..\evil' on a Linux server that later ships the path to Windows).

Source

Thrown at studio/backend/models/export.py:32

    if value is None:
        raise ValueError("save_directory is required")
    raw = str(value).strip()
    if not raw:
        raise ValueError("save_directory must not be empty")
    if "\x00" in raw:
        raise ValueError("save_directory may not contain null bytes")
    if any(ch in raw for ch in ("\r", "\n")):
        raise ValueError("save_directory may not contain control characters")
    path = Path(raw).expanduser()
    path_parts = (*path.parts, *PureWindowsPath(raw).parts, *raw.replace("\\", "/").split("/"))
    if any(len(part) > 255 for part in path_parts if part not in ("", ".", "/", "\\")):
        raise ValueError("save_directory path components must be <= 255 characters")
    if (
        ".." in path.parts
        or ".." in PureWindowsPath(raw).parts
        or ".." in raw.replace("\\", "/").split("/")
    ):
        raise ValueError("save_directory may not contain '..' segments")
    return raw


class LoadCheckpointRequest(BaseModel):
    """Request for loading a checkpoint into the export backend."""

    checkpoint_path: str = Field(..., description = "Path to the checkpoint directory")
    max_seq_length: int = Field(
        2048,
        ge = 128,
        le = 32768,
        description = "Maximum sequence length for loading the model",
    )
    load_in_4bit: bool = Field(
        True,
        description = "Whether to load the model in 4-bit quantization",
    )
    trust_remote_code: bool = Field(

View on GitHub (pinned to 203007d190)

Solutions

  1. Send an explicit destination path without '..' — resolve relative paths client-side first (os.path.abspath / path.resolve()) before including them in the payload.
  2. In the client, reject any user input containing '..' components for destination fields.
  3. If a traversal attempt shows up in server logs, treat it as a security signal and audit the calling client.

Example fix

# before
payload = {"save_directory": "../../shared/exports"}
# after
payload = {"save_directory": str((BASE_DIR / "exports" / name).resolve())}
Defensive patterns

Strategy: validation

Validate before calling

def save_directory_no_traversal(payload: dict) -> bool:
    v = payload.get("save_directory")
    if not isinstance(v, str):
        return False
    parts = v.replace("\\", "/").split("/")
    return ".." not in parts

Type guard

def is_traversal_free_path(v: str) -> bool:
    return ".." not in v.replace("\\", "/").split("/")

Prevention

When it happens

Trigger: Sending save_directory like '../../etc', 'outputs/../../home/user/.ssh', or '..\..\C:\Windows' — accidental relative-path joining in clients, or deliberate traversal attempts against the export endpoint.

Common situations: Clients that join a user-supplied relative path onto a base directory without normalizing; security testing; path fields sourced from unvalidated URL parameters.

Related errors


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