Skip to content

Harden runtime_env zip extraction path containment#63786

Merged
edoakes merged 5 commits into
ray-project:masterfrom
H4ck2:harden-runtime-env-zip-containment
Jun 8, 2026
Merged

Harden runtime_env zip extraction path containment#63786
edoakes merged 5 commits into
ray-project:masterfrom
H4ck2:harden-runtime-env-zip-containment

Conversation

@H4ck2

@H4ck2 H4ck2 commented Jun 2, 2026

Copy link
Copy Markdown

Why are these changes needed?

This PR hardens runtime_env zip package extraction by resolving candidate
member paths before checking whether they remain inside the extraction target.

The existing unzip_package() implementation builds a candidate path from the
target directory and zip member name, then checks containment before resolving
path components such as ... This makes the zip extraction path inconsistent
with the safer resolved-path containment logic already used by untar_package().

This change updates unzip_package() to:

  • resolve the extraction target path
  • resolve each candidate zip member extraction path
  • skip zip entries whose resolved path is outside the target directory
  • use the resolved path for directory creation, file writes, and chmod

This keeps zip extraction behavior aligned with the intended path containment
invariant and with the existing tar extraction implementation.

Related issue number

N/A

Checks

  • I've run relevant checks for this change.

Testing

python3 -m py_compile python/ray/_private/runtime_env/packaging.py python/ray/tests/test_runtime_env_packaging.py
git diff --check

I also ran the three new regression cases in a focused standalone pytest harness
against the patched unzip_package() implementation: 3 passed.

@H4ck2 H4ck2 requested a review from a team as a code owner June 2, 2026 03:17

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the path traversal protection logic in unzip_package by resolving paths using os.path.realpath and checking if the resolved path is within the target directory. It also adds unit tests to verify that malicious zip entries attempting path traversal are correctly skipped. The reviewer noted that using startswith for path containment checks is fragile and error-prone, particularly on Windows due to case-insensitivity and drive letters, and suggested using os.path.commonpath instead to ensure robust path validation.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines 1157 to 1161
if not (
resolved == target_real or resolved.startswith(target_real + os.sep)
):
logger.warning(f"Skipping unsafe path in zip: {member}")
continue

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using startswith for path containment checks can be fragile and error-prone, especially on Windows where paths are case-insensitive and can reside on different drives. For example, if there is a casing mismatch (such as drive letter C: vs c: or directory casing), startswith will perform a case-sensitive comparison and incorrectly skip valid files.

Using os.path.commonpath is much more robust as it automatically handles case-normalization on Windows (via os.path.normcase internally), correctly handles path components (avoiding trailing slash issues), and safely raises a ValueError if paths are on different drives, which can then be logged appropriately.

Suggested change
if not (
resolved == target_real or resolved.startswith(target_real + os.sep)
):
logger.warning(f"Skipping unsafe path in zip: {member}")
continue
try:
if os.path.commonpath([target_real, resolved]) != target_real:
logger.warning(f"Skipping unsafe path in zip: {member}")
continue
except ValueError:
logger.warning(f"Skipping path on different drive in zip: {member}")
continue

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion. I updated the containment check to use os.path.commonpath() on the resolved paths, with os.path.normcase() for comparison and ValueError handling for different drives.

Signed-off-by: wonyunkang <rojo.wk@gmail.com>
@H4ck2 H4ck2 force-pushed the harden-runtime-env-zip-containment branch from 4ccfae7 to db0fe0a Compare June 2, 2026 03:38
@ray-gardener ray-gardener Bot added core Issues that should be addressed in Ray Core community-contribution Contributed by the community labels Jun 2, 2026
@edoakes edoakes added the go add ONLY when ready to merge, run all tests label Jun 2, 2026

@edoakes edoakes left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM assuming tests pass. Thanks for the contribution.

@edoakes edoakes enabled auto-merge (squash) June 2, 2026 23:34
@github-actions github-actions Bot disabled auto-merge June 3, 2026 21:32

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 16f9b36. Configure here.

Comment thread python/ray/_private/runtime_env/packaging.py Outdated
@edoakes edoakes enabled auto-merge (squash) June 4, 2026 01:03
auto-merge was automatically disabled June 4, 2026 07:42

Head branch was pushed to by a user without write access

@H4ck2 H4ck2 force-pushed the harden-runtime-env-zip-containment branch from 92883ac to 169c5ab Compare June 4, 2026 07:47
Signed-off-by: H4ck2 <H4ck2@users.noreply.github.com>
@H4ck2 H4ck2 force-pushed the harden-runtime-env-zip-containment branch from 169c5ab to 82444d1 Compare June 4, 2026 08:39
@H4ck2

H4ck2 commented Jun 5, 2026

Copy link
Copy Markdown
Author

This looks like an infra/network flake rather than a PR failure: the retry also failed while pip was fetching psutil metadata from files.pythonhosted.org during Docker/setup, before tests ran. It may pass on a fresh retry or different worker once the network issue clears. I don’t see evidence that this is caused by the PR changes. Could we retry CI when appropriate?

@edoakes

edoakes commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Kicked off CI to re-run, will monitor

@edoakes edoakes merged commit 2a4e49a into ray-project:master Jun 8, 2026
5 of 6 checks passed
@edoakes

edoakes commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the fix @H4ck2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community-contribution Contributed by the community core Issues that should be addressed in Ray Core go add ONLY when ready to merge, run all tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants