unslothai/unsloth · error · HTTPException
Directory not allowed
Error message
Directory not allowed
What it means
HTTP 403 returned by the local-models listing endpoint when _resolve_allowed_models_dir rejects the requested models_dir. The resolver confines models_dir to an allowlist: ./models, the HF cache dir, the legacy HF dir, the default HF dir, the studio root, and the outputs root. Any directory outside that set is refused so the endpoint cannot be used as a general filesystem browser.
Source
Thrown at studio/backend/hub/services/models/local_inventory.py:896
async def _scan_local_models_response(
models_dir: str, custom_folders: list[dict], sources: _LocalInventorySources
) -> LocalModelListResponse:
"""List local model candidates from every supported on-device source."""
hf_cache_dir, legacy_hf, hf_default, lm_dirs, ollama_dirs, known_hf_caches = sources
allowed_roots: list[Path] = [Path("./models").resolve(), hf_cache_dir]
if _safe_is_dir(legacy_hf):
allowed_roots.append(legacy_hf)
if _safe_is_dir(hf_default):
allowed_roots.append(hf_default)
allowed_roots.extend([studio_root(), outputs_root()])
try:
models_root = _resolve_allowed_models_dir(models_dir, allowed_roots)
except ValueError:
raise HTTPException(status_code = 403, detail = "Directory not allowed")
try:
local_models = await _collect_models_from_default_sources(
models_root,
hf_cache_dir,
legacy_hf,
hf_default,
lm_dirs,
ollama_dirs,
known_hf_caches,
custom_folders,
)
models = _dedupe_local_models(_filter_hidden_models(local_models))
return LocalModelListResponse(
models_dir = str(models_root),
hf_cache_dir = str(hf_cache_dir),
lmstudio_dirs = [str(d) for d in lm_dirs],
ollama_dirs = [str(d) for d in ollama_dirs],View on GitHub (pinned to 203007d190)
Solutions
- Omit models_dir to use the default ./models, or set it to the active HF cache directory.
- Move/symlink the desired folder under an allowed root (e.g. ln -s /data/models ./models/my-models).
- For arbitrary folders, use the scan-folders registration endpoint (add_scan_folder) rather than models_dir, after confirming the folder passes its denylist.
Example fix
# before GET /api/models/local?models_dir=/data/llama # after GET /api/models/local?models_dir=/root/project/models # or register /data/llama as a scan folder
Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
def is_allowed_models_dir(p: str, allowed_roots: list[Path]) -> bool:
try:
real = Path(p).resolve()
except (OSError, ValueError):
return False
return any(
real == root.resolve() or root.resolve() in real.parents
for root in allowed_roots
) Try / catch
resp = client.get("/api/models/local", params={"models_dir": d})
if resp.status_code == 403:
# policy refusal, not a bug: switch to default dir or register a scan folder Prevention
- Treat models_dir as a curated knob, not a free-form browser.
- Register arbitrary folders through the scan-folders API instead of models_dir.
- Remember symlinks are resolved — a link escaping the allowed roots is still denied.
When it happens
Trigger: Calling GET /api/models/local with models_dir=/home/alice/my-models (or any path outside the allowed roots); a symlinked models_dir whose realpath escapes the allowed roots after normalization.
Common situations: Front-end state persisting an old models_dir after the cache moved; scripts hitting the API directly with an arbitrary path; users expecting to browse any folder — they must instead register it via the scan-folders API which has its own policy.
Related errors
- Local (stdio) MCP servers can only be configured from the Un
- Password change required
- Path must be a folder or model weight file
- Failed to list local models: {str(e)}
- The filesystem root cannot be registered
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/fb5e8963ea9021fd.
Report an issue: GitHub.