Repository navigation
馃悰 fix(seed): contain metadata cleanup - #3428
Merged
gaborbernat merged 3 commits intoOct 17, 2026
Merged
Conversation
Reported by Anthropic's OSS Scanner: ANT-2026-SXOEHWP7.
A blank top_level.txt line (or a "." entry) resolves to site-packages, which is inside the environment, so cleanup removed the whole directory before reseeding. Entries that resolve to a parent of the dist-info directory are now skipped with the same warning as entries outside the environment. The tests now also cover a RECORD entry that escapes the environment through "..", and a RECORD-listed symlink that points outside: the link is removed, its target is kept.
gaborbernat
force-pushed
the
security/seed-metadata-containment
branch
from
October 17, 2026 10:16
d0c89ee to
b35b807
Compare
gaborbernat
marked this pull request as ready for review
October 17, 2026 10:19
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.
The
app-dataseeder, the default, reseeds a path that already holds an environment: before it copies or links a seed package intosite-packages,_uninstall_previous_versionfinds that package's existing.dist-infodirectory and removes what its metadata lists. The paths came fromtop_level.txtandRECORDinside the environment and were used as found. A blank line, a.or an absolute path intop_level.txtnamedsite-packagesor the environment root. ARECORDentry with.., or one below a symlinked directory, named a path outside the environment. Runningvirtualenvagain over such an environment deleted all ofsite-packages, the whole environment including its interpreter, or files outside it, and reported success. 馃悰 Cleanup now removes an entry only when it resolves to a strict descendant of the environment root that is not an ancestor of the metadata directory, and removes a symlink as a link. Everything else is skipped with a warning. This addresses draft advisory ANT-2026-SXOEHWP7.Package metadata supplied the removal paths
_uninstall_dist, added in #2186 so that a reseeded wheel does not leave the previous version's entry points behind, built the removal set from each stripped line oftop_level.txtjoined tosite-packages, the.dist-infodirectory itself, and the first field of eachRECORDline joined to the same base. It deleted every path that existed, a directory withshutil.rmtreethroughsafe_deleteand the rest withunlink.pathlibdrops an empty or.segment and takes an absolute one as a new anchor, so a blank line or.namedsite-packages, and the environment's own path named its root.RECORDpaths are relative tosite-packages, and..segments walked out of it.Each entry is now resolved and checked before removal.
Path.resolvefollows symlinks and folds.., and the entry is kept only when the resolved environment root is among itsparents, which excludes the root itself, and when it is not among the resolved parents of the.dist-infodirectory, which excludessite-packagesand every directory between it and the root. An entry that fails either test is logged at warning level with the metadata file that listed it and left in place. The rest is removed as before. ARECORDentry that leavessite-packagesbut stays inside the environment, such as the../../bin/piplaunchers, still passes.flowchart LR E[entry from top_level.txt or RECORD] --> B[before: exists, so rmtree or unlink] E --> L{is a symlink?} L -- yes --> RL[resolve the parent, keep the link name] L -- no --> RF[resolve the whole path] RL --> C{root among parents and not above the dist-info?} RF --> C C -- no --> W[warn and keep] C -- yes --> D[rmtree a directory, unlink the rest] classDef bad fill:#ffd6d6,stroke:#c1121f,color:#5c0000; classDef ok fill:#d8f3dc,stroke:#2d6a4f,color:#1b4332; class B bad; class W,D ok;A symlink above the entry was followed
The old loop tested
exists(), which follows symlinks, and thenis_dir() and not is_symlink(). An entry that was itself a symlink was unlinked and its target kept, while a dangling link failedexists()and stayed. An entry below a symlinked directory,linked/keep.txtwithlinkedpointing outside the environment, counted as a regular file, andunlinkremoved the file inside the link's target; a directory below such a link went tormtreethe same way.The loop now takes
is_symlink()besideexists(), so a dangling link is an entry too, and resolves a link through its parent only,entry.parent.resolve() / entry.name. The link's own location decides containment, andPath.unlinkremoves the link and not its target. Any other entry resolves in full, so a symlinked directory above it moves the resolved path outside the root and the entry is skipped.sequenceDiagram participant C as cleanup participant L as site-packages/linked (symlink) participant O as outside directory rect rgb(255, 214, 214) C->>L: RECORD lists linked/keep.txt: exists, not a directory L->>O: unlink through the link: outside/keep.txt removed end rect rgb(216, 243, 220) C->>C: resolve: outside is not under the root, warn and keep C->>L: RECORD lists linked itself: unlink the link Note over O: target untouched endBehavior changes
Cleanup skips entries that resolve outside the environment, to the environment root, to
site-packagesor to a directory between the two, with a warning; before, they were deleted. Launcher records inside the environment remain supported. A final symbolic link is removed without following its target, as before, and a dangling one listed byRECORDis now removed as well.Reported by Anthropic's OSS Scanner: ANT-2026-SXOEHWP7.