{"record":{"id":"5638729a2d87c0ad","repo":"langflow-ai/langflow","slug":"folder-must-be-one-of-join-sorted-allowed-f","errorCode":null,"errorMessage":"Folder must be one of: {', '.join(sorted(allowed_folders))}","messagePattern":"Folder must be one of: (.+?)","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"src/backend/base/langflow/api/v1/files.py","lineNumber":210,"sourceCode":"        raise HTTPException(status_code=500, detail=str(e)) from e\n\n\n@router.get(\"/profile_pictures/{folder_name}/{file_name}\")\nasync def download_profile_picture(\n    folder_name: ValidatedFolderName,\n    file_name: ValidatedFileName,\n    settings_service: Annotated[SettingsService, Depends(get_settings_service)],\n):\n    \"\"\"Download profile picture from local filesystem.\n\n    Profile pictures are first looked up in config_dir/profile_pictures/,\n    then fallback to the package's bundled profile_pictures directory.\n    \"\"\"\n    try:\n        # Only allow specific folder names (dynamic from config + package)\n        allowed_folders = _get_allowed_profile_picture_folders(settings_service)\n        if folder_name not in allowed_folders:\n            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\")","sourceCodeStart":192,"sourceCodeEnd":228,"githubUrl":"https://github.com/langflow-ai/langflow/blob/976ec789d2886a86de109c044d089d68e96c9a35/src/backend/base/langflow/api/v1/files.py#L192-L228","documentation":"HTTP 400 from GET /files/profile_pictures/{folder_name}/{file_name} when folder_name is not in the allow-list computed by _get_allowed_profile_picture_folders(settings_service) — the set of folder names derived from the config dir's profile_pictures/ tree plus the package's bundled profile_pictures directories. The message enumerates the sorted allowed names so the caller can self-correct.","triggerScenarios":"GET /files/profile_pictures/{unknown}/{file} with a folder not present under config_dir/profile_pictures/ or the bundled set; typos in folder names ('spaces' vs 'space', case sensitivity); requesting a folder that exists on one deployment but not another.","commonSituations":"Custom profile picture sets added to a different instance; renaming theme folders; frontend hardcoding a folder name that the deployment never provisioned.","solutions":["Use one of the folder names listed verbatim in the error detail","Create the folder under <config_dir>/profile_pictures/ with at least one image so it enters the allow-list","Check case-sensitivity — the comparison is exact"],"exampleFix":"# before\nGET /files/profile_pictures/Avatars/avatar_1.png  # 400\n\n# after\nmkdir -p ~/.langflow/profile_pictures/Avatars  # add image\ncp avatar_1.png ~/.langflow/profile_pictures/Avatars/\nGET /files/profile_pictures/Avatars/avatar_1.png","handlingStrategy":"validation","validationCode":"ALLOWED_FOLDERS = {\"defaults\", \"custom\"}  # sync with server config\n\ndef folder_allowed(folder_name: str, allowed: set[str]) -> bool:\n    return folder_name in allowed","typeGuard":"function isAllowedProfileFolder(folder: string, allowed: string[]): boolean {\n  return allowed.includes(folder);\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 == 400:\n        allowed = parse_allowed_folders(e.response.text)\n        client.get(f\"/files/profile_pictures/{pick(allowed)}/{name}\")","preventionTips":["Fetch the folder list from the error/config once and cache it client-side","Provision custom picture folders on every deployment before referencing them","Match folder names exactly — the check is case-sensitive"],"tags":["files","profile-pictures","http-400","validation"],"backgroundTag":null,"analyzedSha":"976ec789d2886a86de109c044d089d68e96c9a35","analyzedAt":"2026-08-14T18:23:12.227Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}