unslothai/unsloth · error · HTTPException

Permission denied reading {current.name}

Error message

Permission denied reading {current.name}

What it means

Raised as a 403 by _match_browse_child when the backend, while walking from an allowlist root toward the requested browse path, hits a PermissionError calling iterdir() on an intermediate directory. It means the backend process lacks read permission on that directory, so it cannot confirm the next path component exists.

Source

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

    if rel_text == ".." or rel_text.startswith(f"..{os.sep}"):
        return None

    parts = [part for part in rel_text.split(os.sep) if part not in ("", ".")]
    altsep = os.altsep
    for part in parts:
        if part == ".." or os.sep in part or (altsep and altsep in part):
            return None
    return parts


def _match_browse_child(current: Path, name: str) -> Optional[Path]:
    """Return the immediate child named ``name`` under ``current``."""
    try:
        for child in current.iterdir():
            if child.name == name:
                return child
    except PermissionError:
        raise HTTPException(
            status_code = 403,
            detail = f"Permission denied reading {current.name}",
        ) from None
    except OSError as exc:
        logger.warning("browse-folders: could not read %s: %s", current, exc, exc_info = True)
        raise HTTPException(
            status_code = 500,
            detail = f"Could not read {os.path.basename(str(current))}",
        ) from exc
    return None


def _resolve_browse_target(path: Optional[str], allowed_roots: list[Path]) -> Path:
    """Resolve a requested browse path by walking from trusted allowlist roots."""
    from storage.studio_db import (
        contains_sensitive_path_component,
        is_denied_system_path,
    )

View on GitHub (pinned to 203007d190)

Solutions

  1. Grant the backend process read+execute permission on the blocking directory: chmod a+rx /restricted/dir or adjust the ACL.
  2. Run the backend as a user that can traverse the path, or move the models under a directory the backend can read.
  3. Register the deepest readable directory as a scan folder via POST /api/models/scan-folders so the walk starts there and skips unreadable parents.

Example fix

# before: walk must pass through /srv/private (mode 700, other user)
GET /api/models/browse-folders?path=/srv/private/models  # 403

# after (backend user can traverse):
# chmod o+rx /srv/private
GET /api/models/browse-folders?path=/srv/private/models
Defensive patterns

Strategy: validation

Validate before calling

import os

def readable(d: str) -> bool:
    try:
        os.listdir(d); return True
    except PermissionError:
        return False

path = '/srv/private/models'
if not all(readable(p) for p in iter_parents(path)):
    raise PermissionError('backend cannot traverse path — fix perms or register a nearer scan folder')

Try / catch

try:
    entries = browse(path)
except HTTPError as e:
    if e.response.status_code == 403 and 'Permission denied' in e.response.json()['detail']:
        show_user('Grant the backend read access to the parent directory')
    else: raise

Prevention

When it happens

Trigger: GET browse-folders with a path whose route from an allowed root passes through a directory owned by another user or with mode 700; running the studio backend as a user without read access; Windows ACLs denying list permission on a parent folder.

Common situations: Models stored under a sibling user's home or a restricted mount; containerized backend running as a different uid than the directory owner; macOS/Linux permission tightening on parent dirs.

Related errors


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