langflow-ai/langflow · warning · HTTPException

Profile picture not found

Error message

Profile picture not found

What it means

HTTP 404 from GET /files/profile_pictures/{folder_name}/{file_name} when the realpath-canonicalized candidate escapes the allowed base directory (config_dir/profile_pictures or the package's bundled profile_pictures). Both the config-dir and package-fallback branches use os.path.realpath + startswith checks; a candidate outside the base is reported as 'not found' (not 'forbidden') to avoid leaking path internals. Genuine missing files fall through to later checks, but this specific raise is the path-traversal guard.

Source

Thrown at src/backend/base/langflow/api/v1/files.py:228

            raise HTTPException(status_code=400, detail=f"Folder must be one of: {', '.join(sorted(allowed_folders))}")

        # SECURITY: Extract only the final path component to prevent path traversal.
        # This is defense-in-depth on top of ValidatedFileName/ValidatedFolderName.
        safe_folder = Path(folder_name).name
        safe_file = Path(file_name).name

        extension = safe_file.split(".")[-1]
        config_dir = settings_service.settings.config_dir

        # SECURITY: use os.path.realpath + startswith — the sanitiser pattern
        # recognised by CodeQL's py/path-injection analysis. realpath canonicalises
        # the path and resolves symlinks, so the subsequent startswith check is
        # robust against both traversal sequences and symlink-based escapes.
        # os.path.join is deliberate here (PTH118) to match CodeQL's sanitiser model.
        allowed_base = os.path.realpath(os.path.join(str(config_dir), "profile_pictures"))  # noqa: PTH118
        candidate = os.path.realpath(os.path.join(allowed_base, safe_folder, safe_file))  # noqa: PTH118
        if candidate != allowed_base and not candidate.startswith(allowed_base + os.sep):
            raise HTTPException(status_code=404, detail="Profile picture not found")
        file_path = Path(candidate)

        # Fallback to package bundled profile pictures if not found in config_dir
        if not file_path.exists():
            from langflow.initial_setup import setup

            package_base = os.path.realpath(str(Path(setup.__file__).parent / "profile_pictures"))
            package_candidate = os.path.realpath(os.path.join(package_base, safe_folder, safe_file))  # noqa: PTH118
            if package_candidate != package_base and not package_candidate.startswith(package_base + os.sep):
                raise HTTPException(status_code=404, detail="Profile picture not found")

            package_path = Path(package_candidate)
            if package_path.exists():
                file_path = package_path
            else:
                raise HTTPException(status_code=404, detail=f"Profile picture {safe_folder}/{safe_file} not found")

        content_type = build_content_type_from_extension(extension)

View on GitHub (pinned to 976ec789d2)

Solutions

  1. Request a plain file name that actually lives under profile_pictures/<folder>/ — no path separators or traversal
  2. Remove/repoint symlinks inside profile_pictures so all targets stay within the directory
  3. Re-check the folder allow-list first; valid folder + plain filename avoids this branch

Example fix

# before
GET /files/profile_pictures/defaults/..%2F..%2Fsecret.png  # 404 guard

# after
GET /files/profile_pictures/defaults/space.png
Defensive patterns

Strategy: validation

Validate before calling

import re

def is_safe_picture_name(file_name: str) -> bool:
    # single path component, no traversal, no separators
    return bool(re.fullmatch(r"[A-Za-z0-9._-]+", file_name)) and ".." not in file_name

Type guard

function isSafePictureName(fileName: string): boolean {
  return /^[A-Za-z0-9._-]+$/.test(fileName) && !fileName.includes("..");
}

Try / catch

try:
    client.get(f"/files/profile_pictures/{folder}/{name}").raise_for_status()
except HTTPError as e:
    if e.response.status_code == 404:
        use_default_picture()  # treat as missing; never probe alternate paths
    raise

Prevention

When it happens

Trigger: file_name containing traversal sequences that survive sanitization into the realpath check (e.g. names whose canonicalization lands outside the base); symlinked files pointing outside profile_pictures/; requesting ../../etc/passwd-style names via the route.

Common situations: Probing URLs with ../ sequences; symlinks in the profile pictures dir pointing elsewhere on disk; security scanners hitting the endpoint.

Related errors


AI-assisted analysis of langflow-ai/langflow@976ec789d2 (2026-08-14). Data as JSON: /api/errors/0f587e6517c1df79. Report an issue: GitHub.