hiyouga/LlamaFactory · error · HTTPException

File access is restricted to the safe media directory.

Error message

File access is restricted to the safe media directory.

What it means

Raised as HTTP 403 by check_lfi_path when local files ARE allowed but os.path.realpath(path) does not fall under the realpath of SAFE_MEDIA_PATH. Even with ALLOW_LOCAL_FILES=true, media access is sandboxed to a single designated directory, blocking ../../ escapes and absolute paths elsewhere on disk.

Source

Thrown at src/llamafactory/api/common.py:63

def jsonify(data: "BaseModel") -> str:
    try:  # pydantic v2
        return json.dumps(data.model_dump(exclude_unset=True), ensure_ascii=False)
    except AttributeError:  # pydantic v1
        return data.json(exclude_unset=True, ensure_ascii=False)


def check_lfi_path(path: str) -> None:
    """Checks if a given path is vulnerable to LFI. Raises HTTPException if unsafe."""
    if not ALLOW_LOCAL_FILES:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Local file access is disabled.")

    try:
        os.makedirs(SAFE_MEDIA_PATH, exist_ok=True)
        real_path = os.path.realpath(path)
        safe_path = os.path.realpath(SAFE_MEDIA_PATH)

        if not real_path.startswith(safe_path):
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN, detail="File access is restricted to the safe media directory."
            )
    except Exception:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid or inaccessible file path.")


def check_ssrf_url(url: str) -> None:
    """Checks if a given URL is vulnerable to SSRF. Raises HTTPException if unsafe."""
    try:
        parsed_url = urlparse(url)
        if parsed_url.scheme not in ["http", "https"]:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Only HTTP/HTTPS URLs are allowed.")

        hostname = parsed_url.hostname
        if not hostname:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid URL hostname.")

        ip_info = socket.getaddrinfo(hostname, parsed_url.port)

View on GitHub (pinned to f28afaf635)

Solutions

  1. Copy or move the media files under the configured SAFE_MEDIA_PATH directory.
  2. Set SAFE_MEDIA_PATH (env/flag at startup) to the directory that already contains your media.
  3. Do not rely on ../ traversal or symlinks — realpath() canonicalizes both.
  4. Prefer data: URLs or an HTTP file server for one-off files.

Example fix

# before
SAFE_MEDIA_PATH=/data/safe ...  # request path: /datasets/img.png -> 403
# after (either copy media or repoint)
cp /datasets/img.png /data/safe/   # then request /data/safe/img.png
Defensive patterns

Strategy: validation

Validate before calling

import os
def inside_safe_media(path, safe_root):
    return os.path.realpath(path).startswith(os.path.realpath(safe_root) + os.sep)

assert inside_safe_media(media_path, SAFE_MEDIA_PATH)

Type guard

const insideSafe = (p, root) => path.resolve(p).startsWith(path.resolve(root) + path.sep);

Try / catch

catch (e) { if (e.status === 403 && e.detail.includes('safe media directory')) { copyIntoSafeDir(mediaPath); retry; } else throw e; }

Prevention

When it happens

Trigger: Local file enabled, but the path is /etc/passwd, /tmp/img.png, or any path whose canonicalized location is outside SAFE_MEDIA_PATH; symlinks inside the safe dir pointing outside (realpath resolves them, so they are rejected too).

Common situations: Enabling local files expecting the old unrestricted behavior; media stored next to the dataset rather than copied into the safe directory; symlinked model datasets.

Related errors


AI-assisted analysis of hiyouga/LlamaFactory@f28afaf635 (2026-08-14). Data as JSON: /api/errors/8becf8aabdfb1d21. Report an issue: GitHub.