[Feature] Centralized Upload Validation (Extension Blacklist & Filename Security)
toinvestigate
- [X] I have searched to see if a similar issue already exists.
Hi Gradio team! I was doing a deep dive into the file upload architecture and noticed a few opportunities to harden the security around file extensions and sanitization. I've mapped out the current flow and put together a proposal for a centralized validation module. I'd love your thoughts on this approach before I open a PR.
#### Overview
This issue addresses two critical security gaps in the current file upload handling:
1. No global blacklist for dangerous file extensions (e.g., `.exe`, `.sh`, `.bat`).
2. Weak filename validation that allows unicode tricks, control characters, and shell-escape sequences.
Currently, these checks are missing at the global backend level. Relying on component-level validation (`gradio/components/file.py`) is optional and leaves the core storage vulnerable if a component doesn't strictly enforce `file_types`.
#### Current Implementation & Vulnerabilities
During a standard upload sequence:
* The frontend validates size but ignores extensions.
* `GradioMultiPartParser` parses the file and checks size/headers, but ignores the extension type.
* `upload_fn()` in `route_utils.py` uses `strip_invalid_filename_characters()` and `safe_join()` before saving to disk.
**The Problem:** `strip_invalid_filename_characters()` is insufficient. It currently allows files like `.bashrc`, unicode lookalikes, null bytes, and Windows reserved names (CON, PRN). Furthermore, because there is no global extension check before storage, dangerous executable files are written to the server's cache directory without friction.
#### Proposed Solution
I propose creating a centralized validation utility that intercepts uploads in `route_utils.py` *before* they are written to disk.
1. Create `gradio/validation.py**`
* `is_extension_allowed(filename, blocked_extensions)`: Extracts the extension and does a case-insensitive check against a blacklist.
* `is_filename_safe(filename)`: Validates length (<= 255), rejects leading/trailing dots and dashes, blocks control characters/null bytes, rejects Windows reserved names, and blocks unicode lookalikes.
2. Update `gradio/blocks.py**`
Add admin-configurable parameters to the `Blocks` class:
* `blocked_file_extensions`: list[str] (Defaulting to common executables and scripts).
* `allow_unsafe_filenames`: bool (Defaulting to False, useful for dev/testing).
3. Update `gradio/route_utils.py**`
In `upload_fn()` (around line 1295, after the initial `strip_invalid_filename_characters` call), add the validation logic:
* If the extension is blocked, raise `HTTPException(400)`.
* If the filename is unsafe, raise `HTTPException(400)`.
#### Testing Strategy
I will add `test/test_upload_validation.py` to ensure:
* `.exe`, `.sh`, and `.bat` files are rejected.
* Custom blacklists passed via `Blocks` are respected.
* Path traversals, dotfiles (`.ssh_key`), leading dashes, and Windows reserved names all correctly trigger an HTTP 400.
#### Out of Scope
To keep this PR focused and reviewable, this proposal strictly focuses on filename and extension security at the storage layer. It does *not* address MIME type mismatch detection, malware scanning (e.g., ClamAV), or refactoring the existing component-level UI validation.
If the core team agrees with this architectural approach, I am happy to own this and open a PR. Let me know if you'd prefer any changes to the ones I've proposed .
1 条评论