unslothai/unsloth · error · HTTPException

Model path is outside Unsloth storage

Error message

Model path is outside Unsloth storage

What it means

Fires when the path to delete is a symlink and even its unresolved (lexical) absolute form does not live under the allowed storage root — outputs_root() for source="training", exports_root() for source="exported" (studio/backend/utils/paths/storage_roots.py:90-96). The endpoint only ever deletes inside Unsloth-managed directories, and symlinks are checked lexically first (via _is_path_under_lexically, models.py:2749) so a link pointing at /etc or the user's home is rejected before resolution. This is a path-containment / anti-traversal guard.

Source

Thrown at studio/backend/routes/models.py:2932

    raw_path = Path(model_path).expanduser()
    if source == "training":
        target_path = raw_path
        allowed_root = outputs_root()
    else:
        allowed_root = exports_root()
        target_path = (
            raw_path.parent
            if export_type == "gguf" and raw_path.suffix.lower() == ".gguf"
            else raw_path
        )

    allowed_root = allowed_root.resolve()
    delete_path = Path(os.path.abspath(str(target_path)))
    delete_path_is_symlink = delete_path.is_symlink()

    if delete_path_is_symlink:
        if not _is_path_under_lexically(delete_path, allowed_root):
            raise HTTPException(
                status_code = 400,
                detail = "Model path is outside Unsloth storage",
            )
        if export_type == "gguf" and gguf_variant:
            target_path = delete_path.resolve()
            if not _is_path_under(target_path, allowed_root):
                raise HTTPException(
                    status_code = 400,
                    detail = "Model path is outside Unsloth storage",
                )
        else:
            target_path = delete_path
    else:
        target_path = target_path.resolve()

    should_check_resolved_path = not delete_path_is_symlink or (
        export_type == "gguf" and gguf_variant
    )

View on GitHub (pinned to 203007d190)

Solutions

  1. Use the exact model_path shown by the studio models list for that row (it always comes from a scan of the correct root).
  2. Check that source matches where the model lives: "training" -> outputs_root(), "exported" -> exports_root().
  3. If you relocated storage, fix the roots (UNSLOTH_OUTPUTS_DIR / equivalent env or settings that outputs_root()/exports_root() read) rather than symlinking from outside.
  4. Verify containment yourself before calling: Path(model_path).expanduser().absolute() must start with the matching root.

Example fix

# before
requests.delete('/delete-finetuned', json={'model_path': '/etc/my-model', 'source': 'training'})
# after
root = Path(os.environ['UNSLOTH_OUTPUTS_DIR']).resolve()
target = Path(model_path).expanduser().absolute()
assert target == root or root in target.parents, 'outside storage'
requests.delete('/delete-finetuned', json={'model_path': str(target), 'source': 'training'})
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

def assert_delete_allowed(model_path: str, source: str, root: Path) -> None:
    lex = Path(os.path.abspath(str(Path(model_path).expanduser())))
    if lex != root and root not in lex.parents:
        raise ValueError(f'{model_path} is outside {root} (lexical symlink check)')

Type guard

def is_inside_storage(model_path: str, root: Path) -> bool:
    p = Path(os.path.abspath(str(Path(model_path).expanduser())))
    return p == root or root in p.parents

Try / catch

try: api_delete(payload) except HTTPError as e: if e.response.status_code == 400 and 'outside Unsloth storage' in e.response.json()['detail']: log_and_reselect_path() else: raise

Prevention

When it happens

Trigger: model_path = "/etc/passwd" with source="training"; model_path = "~/.cache/huggingface/..."; a symlink like outputs/link -> ../../secrets whose abspath spelling itself escapes the root; source set to "training" while passing a path that lives under the exports root (wrong root for that source).

Common situations: Users hand-crafting model_path instead of copying it from the studio UI/scan results; moving or re-pointing an outputs directory with symlinks after a disk change; mixing up source="training" vs "exported" so the wrong root is enforced; attempts to use the delete endpoint as a general file-deletion utility.

Related errors


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