Langflow: Path Traversal in Knowledge Bases API via Creation Endpoint
Description
Langflow's Knowledge Bases API is vulnerable to path traversal, allowing authenticated attackers to create directories and write files anywhere on the server filesystem.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Langflow's Knowledge Bases API is vulnerable to path traversal, allowing authenticated attackers to create directories and write files anywhere on the server filesystem.
Vulnerability
A path traversal vulnerability exists in Langflow's Knowledge Bases API at POST /api/v1/knowledge_bases due to insufficient sanitization of user-supplied knowledge base names. The create_knowledge_base function in src/backend/base/langflow/api/v1/knowledge_bases.py constructs file paths directly from the name field without proper containment checks. The unsanitized value is concatenated with the user's base directory and passed to kb_path.mkdir(). Following directory creation, the application writes embedding_metadata.json and schema.json into the attacker-controlled path. This affects all Langflow versions prior to the fix implemented in PR #12337, which targets release 1.9.0 [1][2][3].
Exploitation
An authenticated attacker can exploit this by sending a crafted POST /api/v1/knowledge_bases request with path traversal sequences (e.g., ../victim_user/evil_kb) or absolute paths (e.g., /tmp/pwned) in the name field. No special privileges beyond authentication are required. The server processes the request, creating directories and writing application files (embedding_metadata.json and schema.json) at the attacker-specified location on the filesystem [1][2][3].
Impact
Successful exploitation allows an attacker to create directories and write files anywhere on the server's filesystem where the application has write permissions. This can lead to cross-user data compromise (creating directories/files within another tenant's knowledge base space), arbitrary filesystem manipulation (e.g., writing to /app/data), and data overwrite of existing embedding_metadata.json and schema.json files, potentially corrupting existing knowledge bases. Any Langflow instance exposing this endpoint to authenticated users is vulnerable [1][2][3].
Mitigation
The fix was addressed in Pull Request #12337, which introduces the _validate_kb_path_containment() helper function. This helper uses Path.is_relative_to() instead of startswith() to enforce strict path boundaries and prevent prefix-ambiguity bugs. The fix also includes regression tests covering traversal sequences, absolute path injection, and prefix-ambiguity bypass attempts. The merged commit, targeting release 1.9.0, was made on March 26, 2026. Users should upgrade to the patched version as soon as possible. No workaround is documented in the available references [1][2][3].
AI Insight generated on Jun 16, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1- Range: <= 1.8.4
Patches
141d34b204803fix: prevent path traversal in knowledge base create endpoint (#12337)
2 files changed · +148 −13
src/backend/base/langflow/api/v1/knowledge_bases.py+26 −13 modified@@ -35,6 +35,24 @@ router = APIRouter(tags=["Knowledge Bases"], prefix="/knowledge_bases", include_in_schema=False) +def _validate_kb_path_containment(kb_user_path: Path, kb_path: Path, kb_name: str, username: str) -> None: + """Raise 403 if kb_path is not contained within kb_user_path. + + Uses is_relative_to() instead of startswith() to prevent path traversal attacks. + startswith() has a prefix-ambiguity bug: a user named "alice" would incorrectly allow + paths under "alice_evil/" because the string starts with "alice". is_relative_to() performs + proper path containment checking. + """ + if not kb_path.is_relative_to(kb_user_path): + logger.warning( + "Path traversal attempt blocked: user=%s kb_name=%r resolved_path=%s", + username, + kb_name, + kb_path, + ) + raise HTTPException(status_code=403, detail=f"Access denied for knowledge base '{kb_name}'.") + + def _resolve_kb_path(kb_name: str, current_user: CurrentActiveUser) -> Path: """Resolve and validate KB path. @@ -49,18 +67,7 @@ def _resolve_kb_path(kb_name: str, current_user: CurrentActiveUser) -> Path: kb_user_path = (kb_root_path / kb_user).resolve() kb_path = (kb_user_path / kb_name).resolve() - # Security: use is_relative_to() instead of startswith() to prevent path traversal attacks. - # startswith() has a prefix-ambiguity bug: a user named "alice" would incorrectly allow - # paths under "alice_evil/" because the string starts with "alice". is_relative_to() performs - # proper path containment checking. - if not kb_path.is_relative_to(kb_user_path): - logger.warning( - "Path traversal attempt blocked: user=%s kb_name=%r resolved_path=%s", - kb_user, - kb_name, - kb_path, - ) - raise HTTPException(status_code=403, detail=f"Access denied for knowledge base '{kb_name}'.") + _validate_kb_path_containment(kb_user_path, kb_path, kb_name, kb_user) if not kb_path.exists() or not kb_path.is_dir(): raise HTTPException(status_code=404, detail=f"Knowledge base '{kb_name}' not found") @@ -78,11 +85,17 @@ async def create_knowledge_base( kb_root_path = KBStorageHelper.get_root_path() kb_user = current_user.username kb_name = request.name.strip().replace(" ", "_") - kb_path = kb_root_path / kb_user / kb_name # Validate KB name if not kb_name or len(kb_name) < MIN_KB_NAME_LENGTH: raise HTTPException(status_code=400, detail="Knowledge base name must be at least 3 characters") + # Security: resolve paths and validate containment to prevent path traversal attacks. + # A crafted kb_name like "../victim/evil" or an absolute path like "/tmp/evil" must be + # rejected before any directory is created. + kb_user_path = (kb_root_path / kb_user).resolve() + kb_path = (kb_user_path / kb_name).resolve() + _validate_kb_path_containment(kb_user_path, kb_path, kb_name, kb_user) + # Check if KB already exists if kb_path.exists(): raise HTTPException(status_code=409, detail=f"Knowledge base '{kb_name}' already exists")
src/backend/tests/unit/test_knowledge_bases_api.py+122 −0 modified@@ -170,6 +170,128 @@ async def test_create_knowledge_base( data = response.json() assert data["name"] == "New KB" + @patch("langflow.api.v1.knowledge_bases.KBStorageHelper.get_root_path") + async def test_create_kb_path_traversal_single_level( + self, mock_root, client: AsyncClient, logged_in_headers, tmp_path + ): + """Single-level traversal '../victim_user/evil_kb' in POST must be blocked with 400/403. + + VULNERABILITY: the create endpoint builds kb_path = kb_root_path / kb_user / kb_name + without resolve() or is_relative_to(), so '../victim_user/evil_kb' escapes the user dir. + """ + mock_root.return_value = tmp_path + (tmp_path / "activeuser").mkdir(parents=True) + victim_dir = tmp_path / "victim_user" / "evil_kb" + + response = await client.post( + "api/v1/knowledge_bases", + headers=logged_in_headers, + json={ + "name": "../victim_user/evil_kb", + "embedding_provider": "OpenAI", + "embedding_model": "text-embedding-3-small", + }, + ) + + assert response.status_code in (400, 403), ( + f"VULNERABILITY CONFIRMED: create endpoint accepted traversal payload with status {response.status_code}" + ) + assert not victim_dir.exists(), ( + "VULNERABILITY CONFIRMED: path traversal created a directory outside the user's KB root" + ) + + @patch("langflow.api.v1.knowledge_bases.KBStorageHelper.get_root_path") + async def test_create_kb_path_traversal_absolute_path( + self, mock_root, client: AsyncClient, logged_in_headers, tmp_path + ): + """Absolute path in kb_name must be blocked — e.g. '/tmp/evil'. + + VULNERABILITY: kb_root_path / kb_user / '/tmp/evil' resolves to '/tmp/evil' in Python + because Path drops all previous components when a segment starts with '/'. + """ + mock_root.return_value = tmp_path + (tmp_path / "activeuser").mkdir(parents=True) + evil_dir = tmp_path / "evil_absolute" + + response = await client.post( + "api/v1/knowledge_bases", + headers=logged_in_headers, + json={ + "name": str(evil_dir), + "embedding_provider": "OpenAI", + "embedding_model": "text-embedding-3-small", + }, + ) + + assert response.status_code in (400, 403), ( + f"VULNERABILITY CONFIRMED: create endpoint accepted absolute path payload " + f"with status {response.status_code}" + ) + assert not evil_dir.exists(), ( + "VULNERABILITY CONFIRMED: absolute path in kb_name created a directory outside the KB root" + ) + + @patch("langflow.api.v1.knowledge_bases.KBStorageHelper.get_root_path") + async def test_create_kb_path_traversal_prefix_ambiguity( + self, mock_root, client: AsyncClient, logged_in_headers, tmp_path + ): + """Prefix-ambiguity attack on create: user='activeuser', target dir='activeuser_evil'. + + With startswith('/root/activeuser'), the path '/root/activeuser_evil/secret_kb' + incorrectly passes because the string starts with '/root/activeuser'. + is_relative_to() closes this gap and must block the request with 400/403. + """ + mock_root.return_value = tmp_path + + (tmp_path / "activeuser").mkdir(parents=True) + victim_kb = tmp_path / "activeuser_evil" / "secret_kb" + victim_kb.mkdir(parents=True) + + response = await client.post( + "api/v1/knowledge_bases", + headers=logged_in_headers, + json={ + "name": "../activeuser_evil/secret_kb", + "embedding_provider": "OpenAI", + "embedding_model": "text-embedding-3-small", + }, + ) + + assert response.status_code in (400, 403), ( + "VULNERABILITY CONFIRMED: prefix-ambiguity bypass succeeded on create endpoint — " + "startswith() may still be in use instead of is_relative_to()" + ) + assert not (tmp_path / "activeuser_evil" / "secret_kb_new").exists(), ( + "VULNERABILITY CONFIRMED: prefix-ambiguity attack created a directory outside the user's KB root" + ) + + @patch("langflow.api.v1.knowledge_bases.logger.warning") + @patch("langflow.api.v1.knowledge_bases.KBStorageHelper.get_root_path") + async def test_create_kb_path_traversal_logs_warning( + self, mock_root, mock_warning, client: AsyncClient, logged_in_headers, tmp_path + ): + """A traversal attempt on create must emit a warning log with user= and kb_name= context.""" + mock_root.return_value = tmp_path + + (tmp_path / "activeuser").mkdir(parents=True) + (tmp_path / "victim_user" / "secret_kb").mkdir(parents=True) + + await client.post( + "api/v1/knowledge_bases", + headers=logged_in_headers, + json={ + "name": "../victim_user/secret_kb", + "embedding_provider": "OpenAI", + "embedding_model": "text-embedding-3-small", + }, + ) + + mock_warning.assert_called_once() + warning_args = mock_warning.call_args[0] + all_args_str = str(warning_args) + assert "user=" in all_args_str, "Warning log must contain 'user=' in the format string" + assert "kb_name=" in all_args_str, "Warning log must contain 'kb_name=' in the format string" + async def test_create_kb_name_too_short(self, client: AsyncClient, logged_in_headers): response = await client.post( "api/v1/knowledge_bases",
Vulnerability mechanics
Root cause
"Missing path-containment validation before using user-supplied `name` to create filesystem paths allows path traversal."
Attack vector
An authenticated attacker sends a `POST /api/v1/knowledge_bases` request with a crafted `name` field containing path-traversal sequences (e.g., `../victim_user/evil_kb`) or an absolute path (e.g., `/tmp/pwned`). The server constructs a file path from this unsanitized input and creates directories and writes application files (`embedding_metadata.json`, `schema.json`) at the attacker-controlled location [[patch_id=6191473], [ref_id=1], [ref_id=2]].
Affected code
The vulnerability resides in the `create_knowledge_base` function inside `src/backend/base/langflow/api/v1/knowledge_bases.py`. The function concatenates the user-supplied `name` field directly with the user's base directory to build a `kb_path`, which is then used with `kb_path.mkdir()` and for writing `embedding_metadata.json` and `schema.json` without any sanitization or containment check.
What the fix does
The patch introduces `_validate_kb_path_containment()`, which resolves both the user's base path and the candidate path, then calls `Path.is_relative_to()` — instead of the previous `startswith()` check — to enforce that the destination stays within the user's directory. This helper is invoked before `kb_path.mkdir()` in `create_knowledge_base`, blocking traversal payloads with a `403 Forbidden` response [[patch_id=6191473], [ref_id=1]].
Preconditions
- authThe attacker must be an authenticated user of the Langflow instance.
- inputThe Langflow instance must expose the POST /api/v1/knowledge_bases endpoint.
- configThe application process must have write permissions to the target filesystem path.
Generated on Jun 16, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
3News mentions
0No linked articles in our index yet.