You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Skip file and directory symlink entries when the HTTP client zips a local directory for upload.
Keep uploaded directory archives bounded to real files under the selected root before sending them to /api/v1/resources/temp_upload.
Warn when directory packaging produces an empty archive, such as a directory containing only skipped symlinks.
Add regression coverage for file symlinks, out-of-root symlinks, directory symlinks, and empty archives.
Why
Directory uploads are packaged on the client before they reach the server. Once a symlink target is followed into the zip, the server only sees a regular archive entry and cannot tell whether it came from outside the chosen directory. This keeps the local upload boundary enforced at packaging time.
Symlink entries are skipped as aliases, whether they point at files or directories. Real files under the selected root are still included through their normal paths, so in-root symlink targets are not lost. For example, if upload/alias points at upload/data, the archive includes data/... when that real path is walked, but not duplicate alias/... entries.
This PR intentionally does not change the existing behavior for files that vanish or become unreadable while the archive is being written. That zipf.write(...) race predates this patch and has different upload-completeness semantics from symlink hardening.
Validation
uv run --frozen --extra dev ruff check openviking_cli/client/http.py tests/client/test_rebuild_clients.py
uv run --frozen --extra dev ruff format --check openviking_cli/client/http.py tests/client/test_rebuild_clients.py
uv run --frozen --extra test pytest tests/client/test_rebuild_clients.py --no-cov -q
Local server smoke with temporary HOME, config, and storage under /tmp/ov-symlink-smoke.*: uploaded a directory containing regular files plus file, external, and directory symlinks; both the client-created zip and server-stored temp upload contained only keep.txt and nested/nested.txt.
Additional broader check:
OPENVIKING_CLI_CONFIG_FILE=/tmp/openviking-nonexistent-ovcli.conf uv run --frozen --extra test pytest tests/client --no-cov -q currently reports 109 passed and 2 failures in existing relation/watch tests outside this upload path.
The defense-in-depth check calls file_path.resolve() without handling exceptions like FileNotFoundError or PermissionError. These could propagate and fail the entire zip operation if a file vanishes after the is_file() check or is unreadable. Consider catching more exceptions here to skip problematic files or log them appropriately.
Replace the try-except block using relative_to() with pathlib's is_relative_to() method. This avoids using exceptions for control flow and makes the path validation logic more readable and idiomatic.
-try:- file_path.resolve().relative_to(root)-except ValueError:+resolved_path = file_path.resolve()+if not resolved_path.is_relative_to(root):
continue
Suggestion importance[1-10]: 6
__
Why: Replaces exception-based control flow with idiomatic pathlib is_relative_to() for better readability and maintainability, accurately targeting the existing code block.
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
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.
Summary
/api/v1/resources/temp_upload.Why
Directory uploads are packaged on the client before they reach the server. Once a symlink target is followed into the zip, the server only sees a regular archive entry and cannot tell whether it came from outside the chosen directory. This keeps the local upload boundary enforced at packaging time.
Symlink entries are skipped as aliases, whether they point at files or directories. Real files under the selected root are still included through their normal paths, so in-root symlink targets are not lost. For example, if
upload/aliaspoints atupload/data, the archive includesdata/...when that real path is walked, but not duplicatealias/...entries.This PR intentionally does not change the existing behavior for files that vanish or become unreadable while the archive is being written. That
zipf.write(...)race predates this patch and has different upload-completeness semantics from symlink hardening.Validation
uv run --frozen --extra dev ruff check openviking_cli/client/http.py tests/client/test_rebuild_clients.pyuv run --frozen --extra dev ruff format --check openviking_cli/client/http.py tests/client/test_rebuild_clients.pyuv run --frozen --extra test pytest tests/client/test_rebuild_clients.py --no-cov -qHOME, config, and storage under/tmp/ov-symlink-smoke.*: uploaded a directory containing regular files plus file, external, and directory symlinks; both the client-created zip and server-stored temp upload contained onlykeep.txtandnested/nested.txt.Additional broader check:
OPENVIKING_CLI_CONFIG_FILE=/tmp/openviking-nonexistent-ovcli.conf uv run --frozen --extra test pytest tests/client --no-cov -qcurrently reports 109 passed and 2 failures in existing relation/watch tests outside this upload path.