-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Implement --index-strategy to mitigate dependency confusion (#8606) #13773
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Ashutosh0x
wants to merge
16
commits into
pypa:main
Choose a base branch
from
Ashutosh0x:feature/index-priority
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
821b845
Implement --index-priority to mitigate dependency confusion
Ashutosh0x 5e29904
Cleanup: remove analysis files and temporary logs before PR
Ashutosh0x 0a7e41b
Implement --index-mapping for namespace isolation and index pinning (…
Ashutosh0x ec3989a
Fix lint: break long line in package_finder.py
Ashutosh0x ab35123
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] ed88fd1
Refactor: Replace --index-priority with --index-strategy
Ashutosh0x 17110f2
Split PR: Remove --index-mapping and focus on --index-strategy
Ashutosh0x d40897c
Add tests and documentation for --index-strategy
Ashutosh0x cbff6ab
Add functional tests for --index-strategy
Ashutosh0x 924d291
Fix CI: linting, tests, and cleanup
Ashutosh0x b9e8d6b
Ignore custom documentation files
Ashutosh0x c22d575
Fix CI: Corrected first-match logic and cleaned up tests
Ashutosh0x 9e5648b
Fix tests: Use valid indexes instead of raw directories for index URLs
Ashutosh0x d416903
Fix tests: Robust version sorting and added debug info to functional …
Ashutosh0x 52451d6
Fix functional tests: Use --no-build-isolation to avoid setuptools de…
Ashutosh0x 2d04a35
Fix pre-commit: Apply black formatting
Ashutosh0x File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Implement ``--index-strategy`` to allow users to prioritize package indexes in the order they are provided. This helps mitigate dependency confusion attacks by stopping the search after the first index that yields a match. |
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
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| from tests.lib import PipTestEnvironment, TestData | ||
|
|
||
|
|
||
| def test_index_strategy_first_match_functional( | ||
| script: PipTestEnvironment, data: TestData | ||
| ) -> None: | ||
| """ | ||
| Functional test for --index-strategy first-match. | ||
| Index 1: data.index_url("simple") -> contains simple 1.0 | ||
| Index 2: data.index_url("yanked") -> contains simple 1.0, 2.0, 3.0 | ||
|
|
||
| Note: We use --no-build-isolation to avoid needing setuptools from | ||
| the test indexes, since we're only testing index selection logic. | ||
| """ | ||
| # Verify best-match (default) picks 2.0 (3.0 is yanked) | ||
| result = script.pip( | ||
| "install", | ||
| "simple", | ||
| "--dry-run", | ||
| "--no-build-isolation", | ||
| "--index-url", | ||
| data.index_url("simple"), | ||
| "--extra-index-url", | ||
| data.index_url("yanked"), | ||
| ) | ||
| assert ( | ||
| "Would install simple-2.0" in result.stdout | ||
| ), f"Actual output: {result.stdout}" | ||
|
|
||
| # Verify first-match picks 1.0 from the first index (index-url) | ||
| result = script.pip( | ||
| "install", | ||
| "simple", | ||
| "--dry-run", | ||
| "--no-build-isolation", | ||
| "--index-strategy", | ||
| "first-match", | ||
| "--index-url", | ||
| data.index_url("simple"), | ||
| "--extra-index-url", | ||
| data.index_url("yanked"), | ||
| ) | ||
| assert ( | ||
| "Would install simple-1.0" in result.stdout | ||
| ), f"Actual output: {result.stdout}" | ||
|
|
||
|
|
||
| def test_index_strategy_find_links_combo( | ||
| script: PipTestEnvironment, data: TestData | ||
| ) -> None: | ||
| """ | ||
| Verify that find-links are still collected in first-match mode. | ||
| Find-links: data.find_links -> contains 3.0 | ||
| Index-url: data.index_url("simple") -> contains 1.0 | ||
| Even in first-match mode, find-links should be searched first and 3.0 picked. | ||
|
|
||
| Note: We use --no-build-isolation to avoid needing setuptools from | ||
| the test indexes, since we're only testing index selection logic. | ||
| """ | ||
| result = script.pip( | ||
| "install", | ||
| "simple", | ||
| "--dry-run", | ||
| "--no-build-isolation", | ||
| "--index-strategy", | ||
| "first-match", | ||
| "--find-links", | ||
| data.find_links, | ||
| "--index-url", | ||
| data.index_url("simple"), | ||
| ) | ||
| assert ( | ||
| "Would install simple-3.0" in result.stdout | ||
| ), f"Actual output: {result.stdout}" |
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| from tests.lib import TestData, make_test_finder | ||
|
|
||
|
|
||
| def test_index_strategy_best_match(data: TestData) -> None: | ||
| """Test the default 'best-match' strategy searches all indexes.""" | ||
| finder = make_test_finder( | ||
| index_urls=[data.index_url("simple"), data.index_url("yanked")], | ||
| index_strategy="best-match", | ||
| ) | ||
| # data.index_url("simple") has simple 1.0 | ||
| # data.index_url("yanked") has simple 1.0, 2.0, 3.0 | ||
| versions = finder.find_all_candidates("simple") | ||
|
|
||
| # Best match should return versions from all indexes | ||
| version_strs = [str(v.version) for v in versions] | ||
| assert "1.0" in version_strs | ||
| assert "2.0" in version_strs | ||
| assert "3.0" in version_strs | ||
| # We expect 4 candidates: | ||
| # 1.0 from simple index, and 1.0, 2.0, 3.0 from yanked index | ||
| assert len(version_strs) == 4 | ||
|
|
||
|
|
||
| def test_index_strategy_first_match(data: TestData) -> None: | ||
| """Test the 'first-match' strategy stops after the first index with hits.""" | ||
| # Order: Index 1 (v1.0) then Index 2 (v1.0, v2.0, v3.0) | ||
| finder = make_test_finder( | ||
| index_urls=[data.index_url("simple"), data.index_url("yanked")], | ||
| index_strategy="first-match", | ||
| ) | ||
|
|
||
| versions = finder.find_all_candidates("simple") | ||
|
|
||
| # Should stop after Index 1 | ||
| version_strs = [str(v.version) for v in versions] | ||
| assert version_strs == ["1.0"] | ||
|
|
||
|
|
||
| def test_index_strategy_first_match_reversed(data: TestData) -> None: | ||
| """Test first-match stops at the first index even if it contains better versions.""" | ||
| # Order: Index 1 (v1.0, v2.0, v3.0) then Index 2 (v1.0) | ||
| finder = make_test_finder( | ||
| index_urls=[data.index_url("yanked"), data.index_url("simple")], | ||
| index_strategy="first-match", | ||
| ) | ||
|
|
||
| versions = finder.find_all_candidates("simple") | ||
|
|
||
| # Should stop after Index 1 | ||
| version_strs = sorted([str(v.version) for v in versions]) | ||
| assert version_strs == ["1.0", "2.0", "3.0"] | ||
| # Should not have versions from Index 2 (even though 1.0 is duplicate) | ||
| assert len(versions) == 3 | ||
|
|
||
|
|
||
| def test_index_strategy_find_links_priority(data: TestData) -> None: | ||
| """Test that find-links are always collected even in first-match mode.""" | ||
| finder = make_test_finder( | ||
| find_links=[data.find_links], | ||
| index_urls=[data.index_url("simple")], | ||
| index_strategy="first-match", | ||
| ) | ||
|
|
||
| versions = finder.find_all_candidates("simple") | ||
|
|
||
| # Should collect find-links PLUS the first matching index | ||
| version_strs = sorted([str(v.version) for v in versions]) | ||
| # find_links (1.0, 2.0, 3.0) + index_url (1.0) | ||
| assert version_strs == ["1.0", "1.0", "2.0", "3.0"] |
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't make changes to the repo's
.gitignore, if you need to git ignore local files you should create your own user level git ignore, e.g. https://dev.to/fronkan/a-personal-gitignore-even-for-a-single-repository-4o7h