{"record":{"id":"4846d78eb3791764","repo":"langflow-ai/langflow","slug":"invalid-path-resolves-outside-allowed-directory","errorCode":null,"errorMessage":"Invalid path: resolves outside allowed directory","messagePattern":"Invalid path: resolves outside allowed directory","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"src/backend/base/langflow/api/v1/flows_helpers.py","lineNumber":105,"sourceCode":"        candidate = normalized_path\n    else:\n        relative_part = normalized_path.lstrip(\"/\")\n        # os.path.join is deliberate here (PTH118) to match CodeQL's sanitiser model.\n        candidate = os.path.join(base_dir_resolved, relative_part) if relative_part else base_dir_resolved  # noqa: PTH118\n\n    try:\n        resolved_str = os.path.realpath(candidate)\n    except (OSError, ValueError) as e:\n        raise HTTPException(status_code=400, detail=f\"Invalid path: {e}\") from e\n\n    # SECURITY: containment check using os.path.realpath + startswith (CodeQL-recognised).\n    if resolved_str != base_dir_resolved and not resolved_str.startswith(base_dir_resolved + os.sep):\n        if is_absolute:\n            raise HTTPException(\n                status_code=400,\n                detail=\"Absolute path must be within your flows directory\",\n            )\n        raise HTTPException(\n            status_code=400,\n            detail=\"Invalid path: resolves outside allowed directory\",\n        )\n\n    # Return the canonicalised path — safe for subsequent filesystem operations.\n    return Path(resolved_str)\n\n\n# Fields that may be updated via setattr on a Flow ORM instance.\n# Any key not in this set is silently dropped to prevent callers from\n# overwriting internal fields (e.g. ``id``, ``user_id``).\n_UPDATABLE_FLOW_FIELDS: frozenset[str] = frozenset(\n    {\n        \"name\",\n        \"description\",\n        \"data\",\n        \"is_component\",\n        \"endpoint_name\",","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/langflow-ai/langflow/blob/976ec789d2886a86de109c044d089d68e96c9a35/src/backend/base/langflow/api/v1/flows_helpers.py#L87-L123","documentation":"HTTP 400: the (relative) fs_path resolved via realpath to a location outside <data_dir>/flows/<user_id> — i.e. it escapes the allowed directory even though it contained no '..'. This is the symlink-escape branch: a symlink inside the flows dir pointing elsewhere makes the canonicalised path fail the startswith containment check.","triggerScenarios":"A file or directory under flows/<user_id>/ is a symlink to somewhere outside (e.g. \"shared\" -> \"/etc\"), and fs_path goes through it; or a race where the path changed between join and realpath; also any relative path that textually stays inside but canonically lands outside.","commonSituations":"Admin or user created symlinks inside the storage dir to share files or save disk; container setups where the flows dir contains mount-point symlinks; attackers planting a symlink to escalate the write outside the sandbox.","solutions":["Remove or fix symlinks under <data_dir>/flows/<user_id>/ so every entry is a real file/subdirectory.","Copy the target file into the flows directory instead of symlinking it.","Use a plain relative filename with no directory components to avoid touching any symlinks."],"exampleFix":"# server-side, before: flows/<uid>/shared -> /srv/shared (symlink)\n# after: real directory\ncp -rL /srv/shared /data/flows/<uid>/shared  # dereference into a real dir","handlingStrategy":"validation","validationCode":"find /data/flows/<user_id> -type l  # empty output means no symlink-escape risk","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep the flows directory symlink-free (real files and dirs only)","Copy shared files in rather than symlinking","Monitor the flows dir for planted symlinks in multi-tenant setups"],"tags":["security","symlink","path-containment","http-400","fs-path"],"backgroundTag":null,"analyzedSha":"976ec789d2886a86de109c044d089d68e96c9a35","analyzedAt":"2026-08-14T18:23:12.227Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}