Skip to content

uv cache prune can delete directories outside UV_CACHE_DIR if a symlink is present in a cache bucket #19542

@fallintoplace

Description

@fallintoplace

Summary

uv cache prune resolves cache entries with canonicalize() before deleting them. If a symlink is present under a cache bucket, prune deletes the symlink target instead of the symlink itself, which means it can remove directories outside UV_CACHE_DIR.

This is distinct from the earlier symlink-mode issues like #12003 and #13270: even if pruning symlinked installs is unsupported, uv cache prune still should not escape the configured cache root.

Repro

tmp_cache=$(mktemp -d)
victim_root=$(mktemp -d)
mkdir -p "$tmp_cache/environments-v2" "$victim_root/victim-dir"
printf 'payload' > "$victim_root/victim-dir/payload.txt"
ln -s "$victim_root/victim-dir" "$tmp_cache/environments-v2/escape"

UV_CACHE_DIR="$tmp_cache" uv cache prune -v

if [ -e "$victim_root/victim-dir" ]; then
  echo exists
else
  echo deleted
fi

Actual behavior

The target directory outside the cache root is removed:

Pruning cache at: /tmp/...
Removed 1 file (7B)
deleted

Expected behavior

uv cache prune should never delete anything outside UV_CACHE_DIR. In this case it should either:

  • remove the symlink itself, or
  • ignore/reject unexpected symlink entries in cache buckets

but it should not follow the symlink and delete the resolved target.

Likely cause

In crates/uv-cache/src/lib.rs, the prune path canonicalizes entries before passing them to rm_rf:

let path = fs_err::canonicalize(entry.path())?;
summary += rm_rf(path)?;

rm_rf itself uses symlink_metadata, so it behaves safely if it receives the original entry path. The unsafe part is resolving the path first.

The environment-path variant appears to have been introduced in #5286 (479725920), and the archive cleanup code still has the same pattern on current main.

Platform

macOS (Homebrew uv 0.11.14)

Version

Reproduced with:

  • uv 0.11.14 (Homebrew 2026-05-12 aarch64-apple-darwin)
  • current main still contains the same canonicalize(...) -> rm_rf(...) pattern in crates/uv-cache/src/lib.rs

Additional context

Related history:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions