Fall back to a safe name when upload filename sanitization yields a traversal token
All Submissions:
- Have you followed the guidelines stated in CONTRIBUTING.md file?
- Have you checked to ensure there aren't any other open Pull Requests for the desired changed?
Type of change
- Bug fix (non-breaking change which fixes an issue)
Changes To Core Features:
- Have you added an explanation of what your changes do and why you'd like us to include them?
- Have you written new tests for your core changes, as applicable?
- Have you successfully ran tests with your changes locally?
Defect
FINDING-007 (SECURITY, MEDIUM) from 0.9.9a1 pre-release testing: _sanitize_upload_filename in packages/reflex-components-core/src/reflex_components_core/core/_upload.py (introduced by #6753) returns the bare traversal token .. for filenames composed entirely of dot/traversal segments ("..", "./../.", "..\\", "/.."). With no safe segment left it falls through to PureWindowsPath(filename).name, which is .. for those inputs. Handlers using the canonical get_upload_dir() / file.name then point at the upload directory's parent, and the write raises an unhandled IsADirectoryError inside the ndjson streaming generator, producing an HTTP 500 — despite the docstring promising a safe relative path. Confirmed live against the buffered upload endpoint; the streamed path shares the same sanitizer.
Fix
Guard the sanitizer's final result: anything that reduces to "", ".", or ".." now falls back to the bare name upload, which stays inside the upload directory. The guard covers both the absolute/drive-path branch ("/..", "C:\\..") and the relative branch where no safe segment survives. Both buffered and streamed uploads go through this one function, so both paths are fixed. Behavior for everything else is unchanged: normal hostile names ("../../evil.txt" -> evil.txt, "a b<>|.txt" kept), relative directories, and legitimate dot-like names ("...", "....", unicode dot lookalikes) all sanitize exactly as before.
Test plan
- New regression tests in
tests/units/components/core/test_upload.py:test_upload_filename_sanitization_traversal_only_falls_back— parametrized over"..","./../.","..\\","/..","/foo/..","C:\\..","..//..","","."; all fail against unfixed main (sanitizer returned..or"") and pass with the fix.test_buffered_upload_traversal_only_filename_falls_back— buffered path via_upload_file_from_starlette, assertspath == Path("upload")andname == "upload".test_chunk_parser_traversal_only_filename_falls_back— streamed path via the chunk multipart parser, asserts the emitted chunk filename isuploadand data intact.test_upload_filename_sanitization_keeps_dot_like_names—"...","....", and unicode-dot variants unchanged; extended the existing drops-path-segments parametrization with"../../evil.txt"and"a b<>|.txt"to lock in existing behavior.
uv run pytest tests/units/components/core/test_upload.py— 56 passed.uv run ruff check ./uv run ruff format .— clean.uv run pyright reflex tests— 0 errors.- News fragment:
packages/reflex-components-core/news/+upload-sanitizer-dots.bugfix.md.
🤖 Generated with Claude Code
https://claude.ai/code/session_01EMjBXPozsNeQNSBZecNH8x
Generated by Claude Code