{"record":{"id":"0f587e6517c1df79","repo":"langflow-ai/langflow","slug":"profile-picture-not-found","errorCode":null,"errorMessage":"Profile picture not found","messagePattern":"Profile picture not found","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"warning","filePath":"src/backend/base/langflow/api/v1/files.py","lineNumber":228,"sourceCode":"            raise HTTPException(status_code=400, detail=f\"Folder must be one of: {', '.join(sorted(allowed_folders))}\")\n\n        # SECURITY: Extract only the final path component to prevent path traversal.\n        # This is defense-in-depth on top of ValidatedFileName/ValidatedFolderName.\n        safe_folder = Path(folder_name).name\n        safe_file = Path(file_name).name\n\n        extension = safe_file.split(\".\")[-1]\n        config_dir = settings_service.settings.config_dir\n\n        # SECURITY: use os.path.realpath + startswith — the sanitiser pattern\n        # recognised by CodeQL's py/path-injection analysis. realpath canonicalises\n        # the path and resolves symlinks, so the subsequent startswith check is\n        # robust against both traversal sequences and symlink-based escapes.\n        # os.path.join is deliberate here (PTH118) to match CodeQL's sanitiser model.\n        allowed_base = os.path.realpath(os.path.join(str(config_dir), \"profile_pictures\"))  # noqa: PTH118\n        candidate = os.path.realpath(os.path.join(allowed_base, safe_folder, safe_file))  # noqa: PTH118\n        if candidate != allowed_base and not candidate.startswith(allowed_base + os.sep):\n            raise HTTPException(status_code=404, detail=\"Profile picture not found\")\n        file_path = Path(candidate)\n\n        # Fallback to package bundled profile pictures if not found in config_dir\n        if not file_path.exists():\n            from langflow.initial_setup import setup\n\n            package_base = os.path.realpath(str(Path(setup.__file__).parent / \"profile_pictures\"))\n            package_candidate = os.path.realpath(os.path.join(package_base, safe_folder, safe_file))  # noqa: PTH118\n            if package_candidate != package_base and not package_candidate.startswith(package_base + os.sep):\n                raise HTTPException(status_code=404, detail=\"Profile picture not found\")\n\n            package_path = Path(package_candidate)\n            if package_path.exists():\n                file_path = package_path\n            else:\n                raise HTTPException(status_code=404, detail=f\"Profile picture {safe_folder}/{safe_file} not found\")\n\n        content_type = build_content_type_from_extension(extension)","sourceCodeStart":210,"sourceCodeEnd":246,"githubUrl":"https://github.com/langflow-ai/langflow/blob/976ec789d2886a86de109c044d089d68e96c9a35/src/backend/base/langflow/api/v1/files.py#L210-L246","documentation":"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.","triggerScenarios":"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.","commonSituations":"Probing URLs with ../ sequences; symlinks in the profile pictures dir pointing elsewhere on disk; security scanners hitting the endpoint.","solutions":["Request a plain file name that actually lives under profile_pictures/<folder>/ — no path separators or traversal","Remove/repoint symlinks inside profile_pictures so all targets stay within the directory","Re-check the folder allow-list first; valid folder + plain filename avoids this branch"],"exampleFix":"# before\nGET /files/profile_pictures/defaults/..%2F..%2Fsecret.png  # 404 guard\n\n# after\nGET /files/profile_pictures/defaults/space.png","handlingStrategy":"validation","validationCode":"import re\n\ndef is_safe_picture_name(file_name: str) -> bool:\n    # single path component, no traversal, no separators\n    return bool(re.fullmatch(r\"[A-Za-z0-9._-]+\", file_name)) and \"..\" not in file_name","typeGuard":"function isSafePictureName(fileName: string): boolean {\n  return /^[A-Za-z0-9._-]+$/.test(fileName) && !fileName.includes(\"..\");\n}","tryCatchPattern":"try:\n    client.get(f\"/files/profile_pictures/{folder}/{name}\").raise_for_status()\nexcept HTTPError as e:\n    if e.response.status_code == 404:\n        use_default_picture()  # treat as missing; never probe alternate paths\n    raise","preventionTips":["Never construct picture paths from user input without sanitization","Keep symlinks out of profile_pictures directories","Treat 404 uniformly (missing or blocked) and fall back to a bundled default"],"tags":["files","profile-pictures","path-traversal","http-404","security"],"backgroundTag":null,"analyzedSha":"976ec789d2886a86de109c044d089d68e96c9a35","analyzedAt":"2026-08-14T18:23:12.227Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}