# REPRO-2026-00246: Gradio FileExplorer path traversal ## Summary Status: published Severity: high Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00246 CVE: CVE-2026-49119 ## Package Name: gradio-app/gradio Ecosystem: github Affected: < 6.16.0 Fixed: 6.16.0 ## Root Cause # RCA Report: CVE-2026-49119 — Gradio FileExplorer Path Traversal ## Summary 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. ## Impact - **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: ```python 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: ```json { "data": ["PRUVA_SECRET_12345"], "is_generating": false, "duration": 0.0002281665802001953, ... } ``` Key excerpt from the fixed run: ```json { "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. ## Reproduction Details Reproduced: 2026-07-06T08:35:50.360Z Duration: 1280 seconds Tool calls: 195 Turns: Unknown Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00246 pruva-verify CVE-2026-49119 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00246&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00246/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-49119 - Source: https://gradio.app/changelog ## Artifacts - bundle/repro/reproduction_steps.sh (reproduction_script, 347 bytes) - bundle/repro/rca_report.md (analysis, 6614 bytes) - bundle/vuln_variant/reproduction_steps.sh (reproduction_script, 312 bytes) - bundle/vuln_variant/rca_report.md (analysis, 7916 bytes) - bundle/artifact_promotion_manifest.json (other, 5106 bytes) - bundle/vuln_variant/source_identity.json (other, 772 bytes) - bundle/vuln_variant/root_cause_equivalence.json (other, 1510 bytes) - bundle/logs/reproduction_steps.log (log, 2220 bytes) - bundle/repro/runtime_manifest.json (other, 645 bytes) - bundle/repro/validation_verdict.json (other, 690 bytes) - bundle/logs/vuln_variant/reproduction_steps.log (log, 5715 bytes) - bundle/vuln_variant/variant_manifest.json (other, 2994 bytes) - bundle/vuln_variant/validation_verdict.json (other, 729 bytes) - bundle/vuln_variant/patch_analysis.md (documentation, 4637 bytes) - bundle/vuln_variant/runtime_manifest.json (other, 1186 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00246 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00246/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00246 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev