unslothai/unsloth · error · HTTPException

Path is not in the browseable allowlist. Register it via POS

Error message

Path is not in the browseable allowlist. Register it via POST /api/models/scan-folders first, or pick a directory under your home folder.

What it means

Raised as a 403 by _resolve_browse_target when a resolved path component lands outside every allowlist root — checked with _is_path_inside_allowlist(resolved_child, resolved_roots) after resolve(). Its main purpose is defeating symlink escapes: a component that exists inside a root but resolves (via symlink) to a location outside all roots is rejected before it is used.

Source

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

                    status_code = 404,
                    detail = f"Path does not exist: {os.path.basename(requested_path)}",
                )
            try:
                resolved_child = child.resolve()
            except OSError as exc:
                logger.warning(
                    "browse-folders: invalid path component %r under %s: %s",
                    part,
                    current,
                    exc,
                    exc_info = True,
                )
                raise HTTPException(
                    status_code = 400,
                    detail = "Invalid path",
                ) from exc
            if not _is_path_inside_allowlist(resolved_child, resolved_roots):
                raise HTTPException(
                    status_code = 403,
                    detail = (
                        "Path is not in the browseable allowlist. Register it via "
                        "POST /api/models/scan-folders first, or pick a directory "
                        "under your home folder."
                    ),
                )
            if contains_sensitive_path_component(str(resolved_child)):
                raise HTTPException(
                    status_code = 403,
                    detail = "Credential or configuration directories are not browseable.",
                )
            if is_denied_system_path(str(resolved_child)):
                raise HTTPException(
                    status_code = 403,
                    detail = "System directories are not browseable.",
                )
            current = resolved_child

View on GitHub (pinned to 203007d190)

Solutions

  1. Register the symlink's real target as a scan folder via POST /api/models/scan-folders, then browse the real path directly.
  2. Remove intermediate symlinks that escape the allowlist, or move/copy the data inside an allowed root.
  3. Keep models under home/studio/outputs roots without escaping symlinks.

Example fix

# before
ln -s /mnt/bigdrive/models ~/models/link
GET /api/models/browse-folders?path=~models/link  # 403

# after
POST /api/models/scan-folders {"path": "/mnt/bigdrive/models"}
GET /api/models/browse-folders?path=/mnt/bigdrive/models
Defensive patterns

Strategy: validation

Validate before calling

import os

def escapes_allowlist(path: str, roots: list[str]) -> bool:
    real = os.path.realpath(path)
    real_roots = [os.path.realpath(r) for r in roots]
    return not any(real == r or real.startswith(r + os.sep) for r in real_roots)

if escapes_allowlist(browse_path, scan_folder_roots):
    raise ValueError('symlink target outside allowlist — register the real target instead')

Try / catch

try:
    entries = browse(path)
except HTTPError as e:
    if e.response.status_code == 403 and 'allowlist' in e.response.json()['detail']:
        register_scan_folder(os.path.realpath(path))  # register real target, retry
    else: raise

Prevention

When it happens

Trigger: Browsing a path that contains a symlink pointing outside home/scan-folder roots (e.g. ~/models/pointers -> /etc); requested_path matching one root's textual prefix but resolving under a different, unlisted tree.

Common situations: Users organizing models with symlinks to a big external drive; linking datasets into home from system paths; moving scan folders and leaving symlinks behind.

Related errors


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