Skip to content

Python/uv library detection queries public PyPI with private project names despite replaces-base: true #16500

Description

@marcguinovart-sec

Is there an existing issue for this?

  • I have searched the existing issues

Package ecosystem

uv, using GitHub-hosted Dependabot in a private repository.

Package manager version

uv 0.12.18 (x86_64-unknown-linux-gnu), as reported in the affected Dependabot job.

Language version

Python 3.11.15, as reported by Dependabot in the affected job.

Manifest location and content before the Dependabot update

The repository contains /pyproject.toml and /uv.lock.

Relevant pyproject.toml excerpt, with the private project name anonymized:

[project]
name = "private-application"

This is the application’s own project name, not a third-party dependency being updated. It is not published on public PyPI.

Dependency packages and lockfile artifacts use our private AWS CodeArtifact index.

dependabot.yml content

Relevant configuration from the affected setup, with private registry identifiers anonymized:

version: 2

registries:
  private-python:
    type: python-index
    url: https://example.d.codeartifact.amazonaws.com/pypi/
    username: aws
    password: $PASSWORD
    replaces-base: true

updates:
  - package-ecosystem: "uv"
    directory: "/"
    registries:
      - private-python
    schedule:
      interval: "daily"

No versioning-strategy override was active in the affected run. The job definition recorded "requirements-update-strategy": null.

Updated dependency

The affected run attempted multiple dependency updates.
The unexpected request uses the application’s project name rather than either dependency name. This report concerns that disclosure, independently of dependency-resolution errors in the same run.

What you expected to see, versus what you actually saw

Expected

With the configured Python index marked replaces-base: true, Dependabot should respect the replacement registry when classifying the project and avoid sending its private project name to public PyPI.

Actual

Dependabot requests:

https://pypi.org/pypi/<-- private-project-name-- >/json/

The request redirects to the endpoint without a trailing slash and returns HTTP 404. The project name has already been sent to public PyPI regardless of the response.

Source evidence

In Python UpdateChecker at commit 89a9e44, check_pypi_for_library_match directly calls:

response = Dependabot::RegistryClient.get(
url: "https://pypi.org/pypi/#{normalised_name(name)}/json/"
)

This method does not check whether configured Python registry credentials have replaces-base: true.

The uv UpdateChecker inherits the Python UpdateChecker.

Could library detection respect replaces-base: true, skip the public lookup, and use an appropriate local-metadata fallback? Regression coverage should verify that this configuration makes no public PyPI request containing the project name.

Native package manager behavior

We have not independently verified native uv behavior using request-level tracing.

The evidence here comes from the hosted Dependabot job and the separate HTTP request implemented in Dependabot’s Ruby update checker.

Images of the diff or a link to the PR, issue, or logs

Sanitized excerpts from the Dependabot run on October 8, 2026.

Relevant job-definition fields; other fields omitted:

{
  "credentials-metadata": [
    {
      "type": "python_index",
      "index-url": "https://example.d.codeartifact.amazonaws.com/pypi/",
      "replaces-base": true
    }
  ],
  "package-manager": "uv",
  "requirements-update-strategy": null,
  "repo-private": true
}

HTTP log excerpt, with the project name redacted:

proxy | 2026/10/08 06:25:46 [018] GET https://pypi.org:443/pypi/<-- private-project-name-- >/json/
proxy | 2026/10/08 06:25:46 [018] 301 https://pypi.org:443/pypi/<-- private-project-name-- >/json/
proxy | 2026/10/08 06:25:46 [020] GET https://pypi.org:443/pypi/<-- private-project-name-- >/json
proxy | 2026/10/08 06:25:46 [020] 404 https://pypi.org:443/pypi/<-- private-project-name-- >/json

Later occurrences in the same run are marked as cached by the proxy. Caching does not prevent the initial disclosure.

Smallest manifest that reproduces the issue

The following is a proposed minimal reproduction based on the observed code path; it has not yet been independently tested:

[project]
name = "private-application"
version = "0.1.0"
description = "Private application"
requires-python = ">=3.11"
dependencies = [
  "requests>=2.31,<3",
]

[[tool.uv.index]]
name = "private-python"
url = "https://example.d.codeartifact.amazonaws.com/pypi/"
default = true

Reproduction steps:

Use a private repository with a project name that is absent from public PyPI.

Generate and commit uv.lock through a working private index, with a dependency version that has an available update.

Configure Dependabot as shown above, including replaces-base: true and no explicit versioning-strategy.

Trigger a Dependabot version-update check.

Inspect the proxy logs for /pypi/<-- private-project-name-- >/json/ requests to public PyPI.

Metadata

Metadata

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions