Repository navigation
fix(system): validate the fail-info download name and path #1454 - #1455
Merged
ulleo merged 2 commits intoNov 10, 2026
Merged
Conversation
The route joined the client-supplied file name onto EXCEL_PATH, checked existence before the suffix and never verified ownership, so a name ending in _error.xlsx could read outside the upload directory and 400/404 disclosed whether an arbitrary path exists. The name must now be a single component, is checked before touching the filesystem, and the resolved path must stay inside the upload directory.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Cause
/system/download-fail-infojoined the client-suppliedfilevalue ontosettings.EXCEL_PATH, checked existence before the suffix, and never verified ownership:Two consequences:
_error.xlsxis opened wherever it points:{"file": "../outside/secret_error.xlsx"}answered200with the file outside the upload directory (POSIX; on Windows the..\..\and drive-relative forms behave the same);400versus404disclosed whether an arbitrary path exists.Fix
safe_upload_name, shared with the upload-side fix in fix(datasource): store Excel uploads under a sanitised name #1448 #1449);os.path.realpath+os.path.commonpath, which also resolves symlinks) and must be a regular file; the response keeps returning only the base name.Tests
backend/tests/test_download_fail_info_path.pymounts the real router on a bare app with the upload directory redirected to a temporary directory and posts real HTTP requests: the normal report is served, a name carrying a directory is not resolved as a path, POSIX / Windows / absolute / drive-relative names never reach the file outside (which stays untouched), a sibling directory sharing the directory prefix is unreachable, an existing file with the wrong suffix and a missing file answer identically, a directory is never downloaded, and a symlink leaving the upload directory is refused.Verification
python -m pytest -q tests/test_download_fail_info_path.py-> 12 passed, 1 skipped (symlink creation is unavailable in the Windows sandbox) after the fix; the same file fails 6 cases against the unmodified source, including the two traversal cases that returned the outside file, run inbackend/on a checkout ofmainat f10255b;ruff checkpasses on the changed files;uv run pytest -qsuite was not run in that sandbox, which has no access to the private xpack index.Fixes #1454
Summary — control-character follow-up
Commit
217184fe9c6ca56798ae626b47a2bb277f4ba231preserves the original commitdb3ff9b3and rejects Unicode control characters (C0, DEL and C1) in the original download name before whitespace trimming or filesystem access. The shared upload-name helper is unchanged.Evidence — control-character follow-up
db3ff9b3produced 21 failed, 6 passed (python -m pytest -q tests/test_download_fail_info_path.py -k control --tb=no).python -m pytest -q tests/test_download_fail_info_path.py tests/test_upload_file_name.py tests/test_excel_http_errors.py, frombackend/, covering normal downloads, traversal/symlink rejection and control characters at the beginning, middle and end of names. The new cases verify identical 400 responses for existing/missing report names and no path resolution or file lookup.ruff check,ruff format --checkon both changed files andgit diff --checkpass.PYTHONPATH=.andLOG_FORMAT='%(levelname)s %(message)s'.Merge Danger
Door: two-way; code-only validation change, no migration.
Blast Radius: downloads. Requests containing control characters now return 400; normal report names retain the existing behavior.