问题描述 / Problem Description
Langchain-Chatchat does not properly restrict knowledge_base_name to the configured KB_ROOT_PATH.
The validation only rejects the literal substring ../. It does not reject absolute paths or verify that the resolved path remains inside KB_ROOT_PATH. When an absolute path is supplied, os.path.join() ignores the configured root.
An unauthenticated user can therefore register an absolute path as a knowledge-base name and subsequently invoke the knowledge-base deletion operation. With the FAISS backend, this reaches shutil.rmtree(self.kb_path) and recursively deletes the selected directory.
The impact is limited to files and directories that the Langchain-Chatchat service account has permission to remove. This report does not claim arbitrary file reading or remote code execution.
复现问题的步骤 / Steps to Reproduce
Safety requirement: perform this test only in an isolated local environment. Use a newly created disposable directory outside KB_ROOT_PATH. Do not use a production system, shared directory, home directory, application directory, or filesystem root.
Configure an isolated Langchain-Chatchat instance with the FAISS vector backend and a disposable KB_ROOT_PATH.
Create a separate disposable victim directory outside KB_ROOT_PATH. Add a marker file and nested directory to it.
Example structure:
<TEST_ROOT>/
├── kb-root/
└── disposable-victim/
├── marker.txt
└── nested/
└── test.txt
Verify that the victim directory is not contained within KB_ROOT_PATH by comparing their canonical or resolved paths.
Send the following request, replacing the placeholder with the absolute path of the disposable victim directory:
POST /knowledge_base/create_knowledge_base
Content-Type: application/json
{
"knowledge_base_name": "<ABSOLUTE_DISPOSABLE_VICTIM_PATH>",
"vector_store_type": "faiss"
}
Verify that:
<ABSOLUTE_DISPOSABLE_VICTIM_PATH>/content was created outside KB_ROOT_PATH.
The absolute knowledge-base name was persisted in the database.
The original marker file still exists.
Send the deletion request using a bare JSON string. The request body must not be a JSON object:
POST /knowledge_base/delete_knowledge_base
Content-Type: application/json
"<ABSOLUTE_DISPOSABLE_VICTIM_PATH>"
Verify that the disposable victim directory, its marker file, and its nested contents no longer exist.
预期的结果 / Expected Result
Langchain-Chatchat should reject absolute paths, traversal sequences, and any knowledge_base_name whose resolved path is outside KB_ROOT_PATH.
The create and delete operations should return an explicit client error, such as HTTP 400 or 403, without:
Creating directories outside KB_ROOT_PATH.
Persisting an invalid absolute knowledge-base name.
Calling a filesystem deletion operation on an outside-root path.
Modifying or deleting the disposable victim directory.
All filesystem operations should independently verify that their resolved target remains beneath KB_ROOT_PATH.
实际结果 / Actual Result
The absolute path was accepted as a knowledge-base name because the validation only checked for the literal substring ../.
During the create request:
os.path.join(KB_ROOT_PATH, absolute_path) resolved to the supplied absolute path.
The application created <ABSOLUTE_DISPOSABLE_VICTIM_PATH>/content outside KB_ROOT_PATH.
The absolute knowledge-base name was persisted in the real SQLite database.
In the tested offline environment, the request returned HTTP 200 with application-level code=500 because no embedding model was configured. The outside-root directory creation and database persistence occurred before this later failure.
During the delete request:
The application reloaded the persisted absolute knowledge-base name.
The request reached the real service chain:
get_service_by_name()
→ load_kb_from_db()
→ clear_vs()
→ drop_kb()
→ faiss_kb_service.do_drop_kb()
→ shutil.rmtree(self.kb_path)
The response returned HTTP 200 with application-level code=200.
The entire disposable victim directory, including its marker file and nested contents, was recursively deleted.
环境信息 / Environment Information
Langchain-Chatchat 版本 / commit 号 / Langchain-Chatchat version / commit number: commit 49165d6
部署方式 / Deployment method: Source deployment in an isolated test environment
使用的模型推理框架 / Model serving method: No embedding inference service configured for the reproduction
使用的 LLM 模型 / LLM used: Not required for the reproduced deletion operation
使用的 Embedding 模型 / Embedding model used: Not configured in the tested offline environment
使用的向量库类型 / Vector library used: FAISS (faiss-cpu)
操作系统及版本 / Operating system and version: Windows
Python 版本 / Python version: 3.11.15
推理使用的硬件 / Inference hardware: Not relevant to the reproduced operation
其他相关环境信息 / Other relevant environment information:
Application interface: Real FastAPI kb_router exercised through TestClient
Persistence: Real SQLite database using create_tables, add_kb_to_db, and load_kb_from_db
Service implementation: Real KBServiceFactory and FAISS KBService classes
langchain==0.1.17
fastapi==0.109.2
starlette==0.36.3
httpx==0.27.2
The security-relevant application functions were not monkey-patched
附加信息 / Additional Information
The root cause is insufficient path-containment validation:
def validate_kb_name(knowledge_base_id: str) -> bool:
if "../" in knowledge_base_id:
return False
return True
def get_kb_path(knowledge_base_name: str):
return os.path.join(
Settings.basic_settings.KB_ROOT_PATH,
knowledge_base_name
)
On Windows and POSIX systems, an absolute second argument can cause os.path.join() to discard the configured base path. Checking only ../ also does not cover Windows separators, UNC paths, drive paths, normalization variants, or symlink-mediated escapes.
The issue is classified as CWE-22: Improper Limitation of a Pathname to a Restricted Directory.
Suggested remediation:
Resolve both KB_ROOT_PATH and the requested target to canonical paths.
Require the target to be a descendant of KB_ROOT_PATH, using Path.relative_to() or os.path.commonpath().
Reject absolute paths and invalid logical knowledge-base names at the API boundary.
Repeat the containment validation immediately before every create, upload, clear, or delete filesystem operation.
Do not use string-prefix comparisons such as startswith() for path containment.
Add authentication and authorization to destructive knowledge-base operations.
Run the service using a least-privileged account.
Recommended regression tests should cover POSIX absolute paths, Windows drive and UNC paths, both slash types, dot segments, normalization variants, symlink escapes, empty names, legitimate ASCII and CJK names, and attempts to bypass route-level validation.
问题描述 / Problem Description
Langchain-Chatchat does not properly restrict knowledge_base_name to the configured KB_ROOT_PATH.
The validation only rejects the literal substring ../. It does not reject absolute paths or verify that the resolved path remains inside KB_ROOT_PATH. When an absolute path is supplied, os.path.join() ignores the configured root.
An unauthenticated user can therefore register an absolute path as a knowledge-base name and subsequently invoke the knowledge-base deletion operation. With the FAISS backend, this reaches shutil.rmtree(self.kb_path) and recursively deletes the selected directory.
The impact is limited to files and directories that the Langchain-Chatchat service account has permission to remove. This report does not claim arbitrary file reading or remote code execution.
复现问题的步骤 / Steps to Reproduce
Safety requirement: perform this test only in an isolated local environment. Use a newly created disposable directory outside KB_ROOT_PATH. Do not use a production system, shared directory, home directory, application directory, or filesystem root.
Configure an isolated Langchain-Chatchat instance with the FAISS vector backend and a disposable KB_ROOT_PATH.
Create a separate disposable victim directory outside KB_ROOT_PATH. Add a marker file and nested directory to it.
Example structure:
<TEST_ROOT>/
├── kb-root/
└── disposable-victim/
├── marker.txt
└── nested/
└── test.txt
Verify that the victim directory is not contained within KB_ROOT_PATH by comparing their canonical or resolved paths.
Send the following request, replacing the placeholder with the absolute path of the disposable victim directory:
POST /knowledge_base/create_knowledge_base
Content-Type: application/json
{
"knowledge_base_name": "<ABSOLUTE_DISPOSABLE_VICTIM_PATH>",
"vector_store_type": "faiss"
}
Verify that:
<ABSOLUTE_DISPOSABLE_VICTIM_PATH>/content was created outside KB_ROOT_PATH.
The absolute knowledge-base name was persisted in the database.
The original marker file still exists.
Send the deletion request using a bare JSON string. The request body must not be a JSON object:
POST /knowledge_base/delete_knowledge_base
Content-Type: application/json
"<ABSOLUTE_DISPOSABLE_VICTIM_PATH>"
Verify that the disposable victim directory, its marker file, and its nested contents no longer exist.
预期的结果 / Expected Result
Langchain-Chatchat should reject absolute paths, traversal sequences, and any knowledge_base_name whose resolved path is outside KB_ROOT_PATH.
The create and delete operations should return an explicit client error, such as HTTP 400 or 403, without:
Creating directories outside KB_ROOT_PATH.
Persisting an invalid absolute knowledge-base name.
Calling a filesystem deletion operation on an outside-root path.
Modifying or deleting the disposable victim directory.
All filesystem operations should independently verify that their resolved target remains beneath KB_ROOT_PATH.
实际结果 / Actual Result
The absolute path was accepted as a knowledge-base name because the validation only checked for the literal substring ../.
During the create request:
os.path.join(KB_ROOT_PATH, absolute_path) resolved to the supplied absolute path.
The application created <ABSOLUTE_DISPOSABLE_VICTIM_PATH>/content outside KB_ROOT_PATH.
The absolute knowledge-base name was persisted in the real SQLite database.
In the tested offline environment, the request returned HTTP 200 with application-level code=500 because no embedding model was configured. The outside-root directory creation and database persistence occurred before this later failure.
During the delete request:
The application reloaded the persisted absolute knowledge-base name.
The request reached the real service chain:
get_service_by_name()
→ load_kb_from_db()
→ clear_vs()
→ drop_kb()
→ faiss_kb_service.do_drop_kb()
→ shutil.rmtree(self.kb_path)
The response returned HTTP 200 with application-level code=200.
The entire disposable victim directory, including its marker file and nested contents, was recursively deleted.
环境信息 / Environment Information
Langchain-Chatchat 版本 / commit 号 / Langchain-Chatchat version / commit number: commit 49165d6
部署方式 / Deployment method: Source deployment in an isolated test environment
使用的模型推理框架 / Model serving method: No embedding inference service configured for the reproduction
使用的 LLM 模型 / LLM used: Not required for the reproduced deletion operation
使用的 Embedding 模型 / Embedding model used: Not configured in the tested offline environment
使用的向量库类型 / Vector library used: FAISS (faiss-cpu)
操作系统及版本 / Operating system and version: Windows
Python 版本 / Python version: 3.11.15
推理使用的硬件 / Inference hardware: Not relevant to the reproduced operation
其他相关环境信息 / Other relevant environment information:
Application interface: Real FastAPI kb_router exercised through TestClient
Persistence: Real SQLite database using create_tables, add_kb_to_db, and load_kb_from_db
Service implementation: Real KBServiceFactory and FAISS KBService classes
langchain==0.1.17
fastapi==0.109.2
starlette==0.36.3
httpx==0.27.2
The security-relevant application functions were not monkey-patched
附加信息 / Additional Information
The root cause is insufficient path-containment validation:
def validate_kb_name(knowledge_base_id: str) -> bool:
if "../" in knowledge_base_id:
return False
return True
def get_kb_path(knowledge_base_name: str):
return os.path.join(
Settings.basic_settings.KB_ROOT_PATH,
knowledge_base_name
)
On Windows and POSIX systems, an absolute second argument can cause os.path.join() to discard the configured base path. Checking only ../ also does not cover Windows separators, UNC paths, drive paths, normalization variants, or symlink-mediated escapes.
The issue is classified as CWE-22: Improper Limitation of a Pathname to a Restricted Directory.
Suggested remediation:
Resolve both KB_ROOT_PATH and the requested target to canonical paths.
Require the target to be a descendant of KB_ROOT_PATH, using Path.relative_to() or os.path.commonpath().
Reject absolute paths and invalid logical knowledge-base names at the API boundary.
Repeat the containment validation immediately before every create, upload, clear, or delete filesystem operation.
Do not use string-prefix comparisons such as startswith() for path containment.
Add authentication and authorization to destructive knowledge-base operations.
Run the service using a least-privileged account.
Recommended regression tests should cover POSIX absolute paths, Windows drive and UNC paths, both slash types, dot segments, normalization variants, symlink escapes, empty names, legitimate ASCII and CJK names, and attempts to bypass route-level validation.