Skip to content

CVE-2026-49119: Verified Repro With Script Download

CVE-2026-49119: Gradio FileExplorer path traversal

CVE-2026-49119 is verified against gradio-app/gradio · github. Affected versions: < 6.16.0. Fixed in 6.16.0. Vulnerability class: Path Traversal. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00246.

REPRO-2026-00246 gradio-app/gradio · github Path Traversal Variant found Jul 6, 2026 CVE entry ↗ .txt
Severity
HIGH
CVSS
7.5
Confidence
HIGH
Reproduced in
21m 20s
Tool calls
195
Spend
$2.33
01 · Overview

What Is CVE-2026-49119?

CVE-2026-49119 is a high-severity path traversal vulnerability (CWE-22) in Gradio's FileExplorer component that lets an unauthenticated attacker read arbitrary files on the Gradio server host. Pruva reproduced it (reproduction REPRO-2026-00246).

02 · Severity & CVSS

CVE-2026-49119 Severity & CVSS Score

CVE-2026-49119 is rated high severity, with a CVSS base score of 7.5 out of 10.

HIGH threat level
7.5 / 10 CVSS base
Weakness CWE-22 — Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

High — serious impact or readily exploitable. Prioritize remediation.

03 · Affected Versions

Affected gradio-app/gradio Versions

gradio-app/gradio · github versions < 6.16.0 are affected.

How to Reproduce CVE-2026-49119

$ pruva-verify REPRO-2026-00246
or curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00246/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-49119

Information disclosure — reproduced
  • reached the target end-to-end
  • on the real production code path
  • high confidence
  • the upstream fix blocks the same trigger
Trigger

FileExplorer payload with ../ traversal segments

Attack chain
  1. /gradio_api/run/predict/ of a running Gradio app with FileExplorer input
Variants tested

Alternate FileExplorer payloads (absolute path and multi-level relative traversal) trigger the same unvalidated path-join sink in FileExplorer.preprocess() on Gradio <6.16.0. The patched commit (6.16.0) blocks all tested payloads via safe_join, so this is not a bypass.

How the agent worked 521 events · 195 tool calls · 21 min
21 minDuration
195Tool calls
151Reasoning steps
521Events
Agent activity over 21 min
Support
16
Hypothesis
2
Repro
286
Judge
19
Variant
194
0:0021:20

Root Cause and Exploit Chain for CVE-2026-49119

Versions: Gradio before 6.16.0 (vulnerable code present at commit 0d670adf41a0b510f7fd745495dce1664d38f0e5).Fixed: Gradio 6.16.0 (fix commit 97d541f3d5fd05b2587a69ecc94b68fe5d2d7004).

Gradio before 6.16.0 contains a path-traversal vulnerability in the FileExplorer component's preprocess() method. When a Gradio app exposes a FileExplorer input, an unauthenticated remote attacker can send API requests containing .. segments as the selected file path. The vulnerable preprocess() implementation joins the attacker-controlled segments directly under the configured root_dir using os.path.join + os.path.normpath, producing a resolved path that escapes the intended root directory. The resulting path is passed to the user-defined prediction function, enabling arbitrary file read (CWE-22) from the Gradio server host.

  • Product / component: gradio Python package, specifically gradio/components/file_explorer.py (FileExplorer component).
  • Affected versions: Gradio before 6.16.0 (vulnerable code present at commit 0d670adf41a0b510f7fd745495dce1664d38f0e5).
  • Fixed version: Gradio 6.16.0 (fix commit 97d541f3d5fd05b2587a69ecc94b68fe5d2d7004).
  • Risk / consequences: High. Any unauthenticated user who can reach the Gradio API can read arbitrary files readable by the Gradio process (e.g., /etc/passwd, application secrets, source code). The attacker only needs to know or guess the relative path structure above the configured root_dir.

Impact Parity

  • Disclosed / claimed maximum impact: Information disclosure / arbitrary file read via directory traversal (info_leak).
  • Reproduced impact from this run: The running vulnerable Gradio app returned the contents of a file outside the configured root_dir (/tmp/gradio_secret.txt) in response to an API call with a .. traversal payload. The fixed commit rejected the same payload and did not return the file contents.
  • Parity: full — the reproduction demonstrates the exact info-leak impact claimed by the ticket.
  • Not demonstrated: N/A (full impact was reached through the real API surface).

Root Cause

In the vulnerable FileExplorer.preprocess() implementation:

file_ = os.path.normpath(os.path.join(self.root_dir, *file))

os.path.join simply concatenates root_dir with the attacker-provided path segments, and os.path.normpath then resolves any .. segments. Because there is no validation that the final resolved path remains inside root_dir, a payload such as ["..", "gradio_secret.txt"] resolves to /tmp/gradio_root/../gradio_secret.txt/tmp/gradio_secret.txt, which is outside the configured root. This behavior was inconsistent with the component's ls() method, which already validated paths via _safe_join.

The fix commit 97d541f3d5fd05b2587a69ecc94b68fe5d2d7004 (PR #13437) routes both preprocess branches through the same _safe_join helper used by ls(). _safe_join raises InvalidPathError when the combined path is absolute or escapes root_dir, blocking the traversal.

Reproduction Steps

  1. Run bundle/repro/reproduction_steps.sh. The script is self-contained and works from any directory; it sets PRUVA_ROOT automatically if not provided.
  2. What the script does:
    • Clones or reuses the Gradio repository in the durable project cache (/data/pruva/project-cache/.../repo).
    • Creates a Python venv in the project cache and installs Gradio as an editable package from the repo.
    • Checks out the vulnerable commit (0d670adf41a0b510f7fd745495dce1664d38f0e5), starts a small Gradio app with a FileExplorer input rooted at /tmp/gradio_root, and uses _frontend=False so the server can run without a built frontend.
    • Waits until the /gradio_api/info health endpoint responds, then sends a crafted POST to /gradio_api/run/predict/ with payload {"data": [[["..", "gradio_secret.txt"]]]}.
    • Verifies that the response contains the secret string PRUVA_SECRET_12345 read from /tmp/gradio_secret.txt, confirming the info leak.
    • Repeats the same test against the fixed commit (97d541f3d5fd05b2587a69ecc94b68fe5d2d7004) and verifies that the secret is no longer returned (the API returns a 500 error with no secret data).
  3. Expected evidence: The vulnerable run logs a 200 response with {"data": ["PRUVA_SECRET_12345"]}. The fixed run logs a 500 response with {"error": "", "visible": true} and no secret content.

Evidence

  • bundle/logs/reproduction_steps.log — full console output from both runs.
  • bundle/logs/server_vuln.log — Gradio server logs for the vulnerable commit.
  • bundle/logs/server_fixed.log — Gradio server logs for the fixed commit (contains the InvalidPathError traceback).
  • bundle/repro/runtime_manifest.json — structured runtime evidence (service started, healthcheck passed, target endpoint reached, proof artifacts listed).
  • bundle/repro/validation_verdict.json — structured verdict confirming the claim.

Key excerpt from the vulnerable run:

{
  "data": ["PRUVA_SECRET_12345"],
  "is_generating": false,
  "duration": 0.0002281665802001953,
  ...
}

Key excerpt from the fixed run:

{
  "error": "",
  "visible": true
}

Recommendations / Next Steps

  • Upgrade to Gradio 6.16.0 or later, which includes the _safe_join validation in FileExplorer.preprocess().
  • Validate all user-provided paths against a known-safe root directory before using them for filesystem operations. Prefer library helpers such as safe_join over raw os.path.join/os.path.normpath for user input.
  • Regression tests should be added that exercise preprocess() with .. and absolute-path payloads, asserting that InvalidPathError (or equivalent) is raised.
  • Defense in depth: run the Gradio service with minimal file-system permissions and avoid placing sensitive files in locations readable by the process.

Additional Notes

  • Idempotency: The reproduction script was run twice consecutively and produced the same confirmed result both times.
  • Environment: The script runs in a clean sandbox using only Python 3.14 and the tools available there. It installs Gradio from source into a project-cache venv, so subsequent runs reuse the build environment while still performing fresh current-run proofs.
  • Limitations: The reproduction uses _frontend=False because the checked-out source repository does not contain the built frontend assets. This is a deployment convenience and does not affect the API surface under test; the real /gradio_api/run/predict/ endpoint is exercised and the vulnerable FileExplorer.preprocess() code path is reached.

Variant Analysis & Alternative Triggers for CVE-2026-49119

Versions: versions (as tested): Gradio before 6.16.0 (vulnerable commit 0d670adf).Fixed: Gradio 6.16.0 (commit 97d541f3).

This variant stage searched for additional triggers and bypasses of the Gradio FileExplorer path traversal (CVE-2026-49119). Two alternate attacker-controlled payloads were confirmed to reach the same vulnerable sink in gradio/components/file_explorer.py:FileExplorer.preprocess() on the unpatched commit (0d670adf):

  1. Absolute path payload: [[["/tmp/gradio_secret.txt"]]] — the os.path.join(self.root_dir, *file) call discards the configured root_dir when the first segment is absolute and returns an arbitrary absolute path.
  2. Deep relative traversal payload: [[["..", "..", "tmp", "gradio_secret.txt"]]] — multiple .. segments normalize to a path outside root_dir.

Both payloads leak the target file on the vulnerable commit, but the patched commit (97d541f3, Gradio 6.16.0) rejects them via the _safe_join/safe_join validation. Therefore, these are alternate triggers of the same root cause, not bypasses of the fix. The latest default-branch commit (a34843eda) still contains the same hardened code.

Fix Coverage / Assumptions

The original fix (commit 97d541f3, included in Gradio 6.16.0) changed FileExplorer.preprocess() to use the component’s existing _safe_join() helper instead of raw os.path.join + os.path.normpath. _safe_join() delegates to gradio.utils.safe_join(), which:

  • Normalizes the path with posixpath.normpath().
  • Raises InvalidPathError if the normalized path is absolute, equals .., or starts with ../.
  • Returns os.path.join(root_dir, normalized_path) only after validation passes.

The fix assumes that all attacker-controlled paths entering the FileExplorer component flow through either preprocess() (for submitted selections) or ls() (for directory browsing). It is correct: both methods now use _safe_join(). The postprocess() path and developer-supplied value do not accept untrusted API input.

What the fix does NOT cover
  • The fix is specific to FileExplorer. Separate Gradio path-handling issues (e.g., historical /file route traversals) involve different sinks and are not bypasses of this CVE.
  • The fix does not resolve symlink attacks inside root_dir; that is a different threat vector.

Variant / Alternate Trigger

Entry point

All payloads are sent via the same unauthenticated HTTP endpoint:

POST /gradio_api/run/predict/
Content-Type: application/json

with a JSON body whose data array contains the serialized FileExplorerData value.

Tested payloads
Variant Payload (data array) Vulnerable commit (0d670adf) Fixed commit (97d541f3)
Original single-level traversal [[["..", "gradio_secret.txt"]]] Leaks secret (200) Blocked (500)
Absolute path [[["/tmp/gradio_secret.txt"]]] Leaks secret (200) Blocked (500)
Deep relative traversal [[["..", "..", "tmp", "gradio_secret.txt"]]] Leaks secret (200) Blocked (500)
Code path
  1. gradio/routes.py receives the POST request and routes it to the prediction function.
  2. The input is deserialized into FileExplorerData (root: list[list[str]]).
  3. FileExplorer.preprocess() (lines 149–156 in gradio/components/file_explorer.py) resolves each inner list through os.path.join(self.root_dir, *file) + os.path.normpath() on the vulnerable commit, or through _safe_join() on the fixed commit.
  4. The resolved path is passed to the user-defined read_file function, which reads it with open().
  • Product / component: gradio Python package, gradio/components/file_explorer.py, FileExplorer component.
  • Affected versions (as tested): Gradio before 6.16.0 (vulnerable commit 0d670adf).
  • Fixed version: Gradio 6.16.0 (commit 97d541f3).
  • Risk: High on the vulnerable version. Any unauthenticated caller can read arbitrary files readable by the Gradio process using either absolute or relative traversal payloads.

Impact Parity

  • Disclosed / claimed maximum impact: Arbitrary file read / information disclosure (info_leak) via directory traversal.
  • Reproduced impact from this variant run: All three payloads returned the secret file contents on the vulnerable commit, demonstrating the same info_leak impact through a different input shape.
  • Parity: full on the vulnerable commit; none on the fixed commit because the patch blocks the payloads.
  • Not demonstrated: No bypass of the fixed commit; no code execution or write primitive.

Root Cause

The same root cause drives all tested payloads: FileExplorer.preprocess() resolves attacker-supplied path segments with os.path.join + os.path.normpath without validating that the resolved path stays within root_dir. Because os.path.join discards the base directory when an absolute segment is present and os.path.normpath collapses .. segments, the attacker can escape the intended sandbox. The fix replaces that unsafe resolution with _safe_join()/safe_join(), which rejects the resulting absolute or ../-prefixed paths.

  • Fix commit: 97d541f3d5fd05b2587a69ecc94b68fe5d2d7004
  • Latest inspected commit: a34843eda (still uses the same _safe_join() hardening)

Reproduction Steps

Run the stage-specific reproduction script:

bash bundle/vuln_variant/reproduction_steps.sh

The script:

  1. Reads the prepared project cache (bundle/project_cache_context.json) to locate the Gradio repo and venv.
  2. Starts a small Gradio app with a FileExplorer input on http://127.0.0.1:17863 for the vulnerable commit.
  3. Sends the three payloads above to /gradio_api/run/predict/ and records whether the secret file contents are returned.
  4. Repeats the same tests against the fixed commit.
  5. Restores the repo to the fixed commit and exits 1 (no bypass) or 0 (if a payload ever leaks on the fixed commit).

Expected output:

  • Vulnerable commit: all three payloads return 200 with PRUVA_SECRET_12345 in the data field.
  • Fixed commit: all three payloads return 500 with no secret leakage.

Evidence

  • Main log: bundle/logs/vuln_variant/reproduction_steps.log
  • Per-test server logs: bundle/logs/vuln_variant/server_{original_traversal,absolute_path,deep_traversal}_{0d670adf,97d541f3}.log
  • Generated verdict: bundle/vuln_variant/validation_verdict.json
  • Runtime manifest: bundle/vuln_variant/runtime_manifest.json
  • Source identity: bundle/vuln_variant/source_identity.json
  • Root-cause equivalence: bundle/vuln_variant/root_cause_equivalence.json

Key excerpt from the vulnerable run (absolute-path payload):

{
  "data": ["PRUVA_SECRET_12345"],
  "is_generating": false,
  "duration": ...
}

Key excerpt from the fixed run (same payload):

{
  "error": "",
  "visible": true
}

Recommendations / Next Steps

  • The existing Gradio 6.16.0 fix is sufficient for this component and these payloads. No additional code change is required.
  • Maintain regression tests that exercise FileExplorer.preprocess() with absolute paths, single .. segments, and multi-level .. chains, asserting that InvalidPathError is raised in each case.
  • For defense in depth, continue running the Gradio process with minimal filesystem permissions and keep secrets outside the process’s readable paths.

Additional Notes

  • Idempotency: The reproduction script was run twice consecutively and produced the same results both times.
  • Environment: The script runs in the existing project-cache venv (Python 3.14) with _frontend=False because the source checkout does not contain built frontend assets. The API endpoint under test is the real production path.
  • Limitations: Only POSIX payloads were tested; Windows-specific separator behaviors were not exercised because the runtime environment is Linux.

CVE-2026-49119 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:001:00
0:00
session startedaccounts/fireworks/models/kimi-k2p7-code · CVE-2026-49119 · REPRO-20
0:01
0:03
web search
0:05
0:07
0:09
web search
0:15
0:16
web search
0:28
0:28
extract_facts
no facts extracted
0:29
0:29
0:29
supportrepro
0:44
0:45
0:45
0:46
0:46
0:47
0:47
0:47
0:49
0:49
0:52
0:52
0:56
0:58
web search
1:00
08 · How to Fix

How to Fix CVE-2026-49119

Upgrade gradio-app/gradio · github to 6.16.0 or later.

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-49119 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-49119

How does the CVE-2026-49119 path traversal work?

An unauthenticated remote attacker who can reach the Gradio API sends an API request containing .. segments as the selected FileExplorer path; the vulnerable preprocess() implementation resolves the path outside root_dir and passes it to the user-defined prediction function, letting the attacker read files such as /etc/passwd or application secrets that are readable by the Gradio process.

Which Gradio versions are affected by CVE-2026-49119, and where is it fixed?

Gradio before 6.16.0 is affected (vulnerable code present at commit 0d670adf41a0b510f7fd745495dce1664d38f0e5); it is fixed in 6.16.0 (fix commit 97d541f3d5fd05b2587a69ecc94b68fe5d2d7004).

How severe is CVE-2026-49119?

It is rated high severity: any unauthenticated user who can reach a Gradio app exposing a FileExplorer input can read arbitrary files readable by the Gradio process, without needing credentials.

How can I reproduce CVE-2026-49119?

Download the verified script from this page and run it in an isolated environment against a Gradio app before 6.16.0 that exposes a FileExplorer input. It sends a request with directory traversal segments and shows a file outside the configured root directory being read, then confirms 6.16.0 blocks the escape.
11 · References

References for CVE-2026-49119

Authoritative sources for CVE-2026-49119 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.