unslothai/unsloth · error · HTTPException
Dataset '{cleaned}' escapes the Studio datasets directory.
Error message
Dataset '{cleaned}' escapes the Studio datasets directory. What it means
HTTP 400 from _resolve_dataset_folder: the resolved dataset folder does not stay under the resolved datasets root (resolve(strict=...).relative_to(root) raised ValueError) or resolution itself failed (OSError). Despite the name passing the single-component cleaning and not being a symlink, its real path escapes the root — e.g. an intermediate component is a symlink, or the root itself moved between resolve() calls.
Source
Thrown at studio/backend/routes/training.py:3625
"""Validate ``name`` (single component, no traversal) and resolve it under the Studio
datasets root. 404 when a read target is missing."""
from utils.paths import datasets_root
cleaned = _clean_diffusion_dataset_name(name)
root = datasets_root().resolve()
folder = root / cleaned
# Reject a symlinked dataset directory and prove the resolved folder stays under root: _safe_dataset_image_path only checks each image path, not the folder.
if folder.is_symlink():
raise HTTPException(
status_code = 400,
detail = f"Dataset '{cleaned}' must not be a symbolic link.",
)
if must_exist and not folder.is_dir():
raise HTTPException(status_code = 404, detail = f"Dataset '{cleaned}' not found.")
try:
folder.resolve(strict = must_exist).relative_to(root)
except (OSError, ValueError):
raise HTTPException(
status_code = 400,
detail = f"Dataset '{cleaned}' escapes the Studio datasets directory.",
)
return folder
# Match diffusion's 4096px decoded-image limit.
_MAX_TRAINING_IMAGE_SIDE = 4096
def _validate_uploaded_training_image(path: Path, original_name: str) -> None:
"""Reject an uploaded training image whose decoded dimensions exceed the per-side limit.
Reads only the header (never img.load()), so a small-payload / huge-dimension file is caught
before it spikes memory. Bytes PIL cannot identify are left as-is (the upload contract accepts
arbitrary bytes under an image extension), so only oversized real images change behaviour."""
from PIL import Image, UnidentifiedImageError
View on GitHub (pinned to 203007d190)
Solutions
- Remove any symlinks in the datasets root's path chain (both the root location and inside it) so folder.resolve() lands under root.resolve().
- Point the datasets-root configuration at a stable, non-symlinked physical path and restart Studio.
- Re-create the dataset as a plain directory under the root.
Example fix
# find symlinks inside the datasets root and in its own path find -L /studio/datasets -maxdepth 2 -type l readlink -f /studio/datasets # confirm root resolves where expected # replace offending links with real dirs
Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
from utils.paths import datasets_root
def resolves_under_root(name: str) -> bool:
root = datasets_root().resolve()
try:
(root / name).resolve().relative_to(root)
return True
except (OSError, ValueError):
return False Type guard
def is_escape_error(exc: HTTPException) -> bool:
return exc.status_code == 400 and 'escapes' in exc.detail Try / catch
try:
folder = _resolve_dataset_folder(name)
except HTTPException as e:
if e.status_code == 400 and 'escapes' in e.detail:
log_security_event(name) # potential tampering with datasets root
raise Prevention
- Keep the datasets root on a plain physical path; avoid symlinked homes or NAS junction points in its ancestry.
- Periodically audit for symlinks inside the datasets root.
- Treat this error as a security signal, not a user error — investigate who created the link.
When it happens
Trigger: A dataset name that survives _clean_diffusion_dataset_name but whose folder, once resolved, is not under the resolved root. Realistic triggers: a parent inside the datasets root is a symlink pointing elsewhere; the datasets root path itself contains a symlink so root was resolved to a different prefix than folder.resolve(); TOCTOU where the folder is swapped for a link between the is_symlink() check and resolve().
Common situations: Datasets root placed under a symlinked home dir (e.g. /var/www -> /srv/www) so prefix comparison mismatches; NAS-managed directories replaced by links mid-session; unusual mount layouts after container volume remapping.
Related errors
- Dataset '{cleaned}' must not be a symbolic link.
- Model path is outside Unsloth storage
- Invalid image filename.
- Invalid block_id: outside upload root
- Path is not in the browseable allowlist. Register it via POS
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/7e59034b35408db8.
Report an issue: GitHub.