Skip to content

uv: versioning-strategy: lockfile-only blocks security updates for transitive dependencies (security_update_not_possible) #16449

Open

Description

@jspiewak

Is there an existing issue for this?

  • I have searched the existing issues

Related but distinct: #16112 (uv drops versioning-strategy values other than lockfile-only).

Package ecosystem

uv

Package manager version

uv 0.12.18 (Dependabot's bundled uv)

Language version

Python 3.12.13

Manifest location and content before the Dependabot update

/pyproject.toml and /uv.lock. The vulnerable package, urllib3, is transitive only: it isn't listed in pyproject.toml, and it comes in via botocore and requests. uv.lock had urllib3 2.7.0.

[project]
name = "example-app"
requires-python = ">=3.12"
dependencies = [
    "pulumi-aws>=7.32.0,<8",   # -> boto3/botocore -> urllib3
]

[tool.uv]
package = false

dependabot.yml content

version: 2
updates:
  - package-ecosystem: "uv"
    directory: "/"
    schedule:
      interval: "monthly"
    versioning-strategy: lockfile-only
    groups:
      all:
        patterns: ["*"]
    ignore:
      - dependency-name: "*"
        update-types: ["version-update:semver-major"]

Updated dependency

urllib3 2.7.0 → 2.8.0 (security update; every open advisory affects < 2.8.0)

What you expected to see, versus what you actually saw

Expected: a security update PR changing only uv.lock. The versioning-strategy docs say lockfile-only will "Only create pull requests to update lockfiles. Ignore any new versions that would require package manifest changes". This fix needs no manifest change. The security updates docs say the only transitive dependencies Dependabot can't update are those that would also need an update to the parent; here neither parent needs one.

Actual: the job fails with security_update_not_possible. The error itself reports the target as resolvable:

| security_update_not_possible | {                                           |
|                              |   "dependency-name": "urllib3",             |
|                              |   "latest-resolvable-version": "2.8.0",     |
|                              |   "lowest-non-vulnerable-version": "2.8.0", |
|                              |   "conflicting-dependencies": []            |
|                              | }                                           |

The log shows the decision is made before any resolution runs. uv lock is only invoked afterwards, to fill in the error details:

14:00:37 INFO Latest version is 2.8.0
14:00:37 INFO Requirements to unlock update_not_possible
...
14:00:40 INFO Running command: pyenv exec uv lock --upgrade-package urllib3==2.8.0 --index <private index>
14:00:43 INFO Process PID: 1937 completed with status: pid 1937 exit 0
14:00:44 INFO The latest possible version of urllib3 that can be installed is 2.8.0

Cause, as far as we can trace it (permalinks at d8bfe06c):

  1. CreateSecurityUpdatePullRequest#requirements_to_unlock tries only can_update?(requirements_to_unlock: :none) when requirements_unlocked_or_can_be? is false.
  2. For Python/uv, requirements_unlocked_or_can_be? is !requirements_update_strategy.lockfile_only?, so it is false under lockfile-only.
  3. :none calls latest_resolvable_version_with_no_unlock, which calls resolver.latest_resolvable_version(requirement: current_requirement_string).
  4. A sub-dependency has no requirements, so current_requirement_string is nil. LockFileResolver#latest_resolvable_version starts with return nil unless requirement, so can_update?(:none) is false and the result is :update_not_possible.

So under lockfile-only, every uv security update for a transitive dependency fails this way, whatever the dependency graph looks like. Lockfile-only is arguably the case where these updates matter most, since the lockfile is the only thing that can change.

Confirmation: changing only versioning-strategy to increase-if-necessary made the next security job on the same commit graph succeed. Its job spec then carried "requirements-update-strategy":null and "lockfile-only":false (see #16112). It logged Requirements to unlock own, then Updating urllib3 from 2.7.0 to 2.8.0, and opened a PR touching only uv.lock. A security update for urllib3 2.6.3 → 2.7.0 in May, before we set lockfile-only, also succeeded.

Native package manager behavior

uv lock --upgrade-package urllib3==2.8.0 on the same pyproject.toml/uv.lock succeeds and changes only uv.lock:

Resolved 58 packages in 4.42s
Updated urllib3 v2.7.0 -> v2.8.0

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

The repository is private, so the job logs aren't public. The excerpts above are from the failing job (job_1602853618) and the succeeding job (job_1602897949). We can share more of the log on request.

Smallest manifest that reproduces the issue

Any uv project with a vulnerable transitive-only dependency pinned in uv.lock, where the fixed version is compatible with the parents, and versioning-strategy: lockfile-only set. The manifest above is enough.

Activity

  1. jspiewak commented on Oct 2, 2026

    @jspiewak
    Author

    We're hitting the same failure in a second private repository. This one is a library (package = true, built with setuptools), unlike the application in the original report (package = false). urllib3 is again transitive only (pulumi-kubernetes → requests → urllib3), locked at 2.7.0, and uv lock --upgrade-package urllib3 resolves 2.8.0 cleanly, changing only uv.lock.

    The job spec has "lockfile-only":true, "allowed-updates":[{"dependency-type":"direct","update-type":"all"}], and "update-subdependencies":false, and the log shows the same early decision:

    INFO <job_1605370789> Latest version is 2.8.0
    INFO <job_1605370789> Requirements to unlock update_not_possible
    INFO <job_1605370789> No update possible for urllib3 2.7.0
    INFO <job_1605370789> Finished job processing
    

    Unlike the original report, this job didn't run uv lock afterwards to fill in the security_update_not_possible details. It finished right after No update possible.

    For libraries, the workaround from the original report (switching to increase-if-necessary) isn't really available. We set lockfile-only because of #14908: the other strategies raise lower bounds in pyproject.toml, and in a library those bounds are the minimum versions consumers get. So a library that uses uv currently has to choose between receiving transitive security updates and keeping its declared lower bounds stable. For now we're applying these fixes by hand.

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

Metadata

Metadata

Assignees

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions