unslothai/unsloth · error · HTTPException

Directory not allowed

Error message

Directory not allowed

What it means

Raised as a 403 by the local-models listing endpoint when the requested models_dir, after expanduser + realpath, is neither equal to nor a subdirectory of any allowed root (home directory, studio root, outputs root, registered scan folders). The check uses resolved paths on both sides, so symlink tricks do not bypass it; the trusted allowlist root is used for the scan, never the user-supplied path.

Source

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

    if _safe_is_dir(legacy_hf):
        allowed_roots.append(legacy_hf)
    if _safe_is_dir(hf_default):
        allowed_roots.append(hf_default)
    try:
        from utils.paths import studio_root, outputs_root
        allowed_roots.extend([studio_root(), outputs_root()])
    except Exception:
        pass

    requested = os.path.realpath(os.path.expanduser(models_dir))
    models_root = None
    for root in allowed_roots:
        root_str = os.path.realpath(str(root))
        if requested == root_str or requested.startswith(root_str + os.sep):
            models_root = root  # trusted root, not the user-supplied path
            break
    if models_root is None:
        raise HTTPException(
            status_code = 403,
            detail = "Directory not allowed",
        )

    try:
        models = await _shared_compat_local_inventory_scan(models_root, sources)
        # Tag each model with its task so the Images picker can filter to diffusion.
        models = [m.model_copy(update = {"task": _local_model_task(m)}) for m in models]

        return LocalModelListResponse(
            models_dir = str(models_root),
            hf_cache_dir = str(hf_cache_dir),
            lmstudio_dirs = [str(d) for d in lm_dirs],
            models = models,
        )
    except Exception as e:
        raise log_and_http_error(
            e,

View on GitHub (pinned to 203007d190)

Solutions

  1. Register the directory first via POST /api/models/scan-folders with the absolute path, then retry the models listing.
  2. Point models_dir at a directory under your home folder, the studio root, or the outputs root.
  3. Verify the realpath: the comparison uses os.path.realpath on both sides, so confirm the resolved location (e.g. readlink -f /your/path) is actually inside an allowed root.
  4. Check for symlinks in the path whose targets escape the allowlist and register the real target instead.

Example fix

# before
resp = client.get('/api/models/local', params={'models_dir': '/mnt/bigdrive/models'})  # 403

# after
client.post('/api/models/scan-folders', json={'path': '/mnt/bigdrive/models'})
resp = client.get('/api/models/local', params={'models_dir': '/mnt/bigdrive/models'})
Defensive patterns

Strategy: validation

Validate before calling

import os

def allowed(models_dir: str, scan_folders: list[str]) -> bool:
    req = os.path.realpath(os.path.expanduser(models_dir))
    roots = [os.path.realpath(os.path.expanduser('~'))] + [os.path.realpath(f) for f in scan_folders]
    return any(req == r or req.startswith(r + os.sep) for r in roots)

Try / catch

try:
    resp = client.get('/api/models/local', params={'models_dir': d})
except HTTPError as e:
    if e.response.status_code == 403:
        register_scan_folder(d); resp = client.get('/api/models/local', params={'models_dir': d})
    else: raise

Prevention

When it happens

Trigger: GET /api/models/local?models_dir=/etc or any path outside the allowlist; a path containing '..' that resolves outside a root; a symlink whose realpath target lies outside all allowed roots; a directory that was never registered via POST /api/models/scan-folders.

Common situations: Front-end remembers a models directory from another machine/user; user pastes /var/data/models or a mount point outside home; models stored on an external drive not registered as a scan folder; case/format differences in the path that make the prefix check fail.

Related errors


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