Skip to content

Poetry 0.x vendored → hosted takeover un-vendors the package before hosted mode refuses the lock, while --dry-run previews a clean takeover (redirected: 1, exit 0) #945

Description

[agent] Found by the scheduled Poetry bug-hunt routine (ledger #311).

Summary

Vendored mode supports Poetry 0.12 locks ([metadata.hashes], no lock-version), but hosted mode refuses every one of them: redirect_poetry_lock_unsupported, "Poetry 0.x ignores URL sources" (docs/testing/poetry-compatibility.md, "Formats and rewrite behavior"). So when you run scan --mode hosted on a project that socket-patch vendored on a 0.12 lock, the takeover can never succeed. It happens anyway:

  • Wet run: it reverts the vendored wiring first. That restores the lock's registry entry and deletes .socket/vendor/pypi/<uuid>/ and the ledger entry. Only then does the Poetry rewriter refuse the lock. The result is partial_failure, redirected: 0, redirect_takeover_unpatched, exit 1. A project that installed patched six before the command now installs the unpatched release.
  • --dry-run: reports status: success, redirected: 1, with only redirect_would_revert_vendored, and exits 0. The refusal is never predicted. A plain hosted --dry-run on the same 0.12 lock (never vendored) does report redirect_poetry_lock_unsupported, so the takeover preview simply never consults the rewriter.

The refusal depends only on the lock's format (lock_version_of(lock) == "0"), which is known before anything is written. It's the Poetry counterpart of #723 (uv) and #699 / #708 (requirements.txt).

Impact

  • Someone who previews with --dry-run, sees a clean takeover, and then runs it loses the patch. The vendored wheel is deleted and the next poetry install gets the vulnerable upstream release. The printed remedy ("re-run scan --mode hosted") can't work on this lock; only scan --mode vendored recovers.
  • Scope is narrow: only projects still on a Poetry 0.12 lock, which vendored mode supports and documents. Every lock from Poetry 1.0 onward takes over cleanly (control rows below).

Repro (Linux, main 9c43dfc, real Poetry 0.12.17)

I used a local mock of the patch API serving a patched six-1.16.0 wheel (batch, by-package, grant with sha256 + sha512, view, wheel), plus SOCKET_PYPI_JSON_API pointed at a PyPI forwarder.

API="--api-url $MOCK --org o --api-token t --patch-server-url $MOCK"
mkdir demo && cd demo && mkdir demo && touch demo/__init__.py
cat > pyproject.toml <<'EOF'
[tool.poetry]
name = "demo"
version = "0.1.0"
description = ""
authors = ["x <x@example.com>"]

[tool.poetry.dependencies]
python = "^3.9"
six = "1.16.0"
EOF
poetry lock        # Poetry 0.12.17: [metadata.hashes]; today's PyPI gives `six = []`.
                   # I filled in the two real sha256s (the historical "populated" shape); the empty shape behaves the same for vendored.
socket-patch scan --mode vendored --yes --ecosystems pypi --json $API      # exit 0; [package.source] type = "file" -> .socket/vendor/...
python3 -m venv .venv && VIRTUAL_ENV=$PWD/.venv poetry install           # six installed PATCHED
socket-patch scan --mode hosted --dry-run --yes --ecosystems pypi --json $API
#   exit 0, status success, redirect.redirected 1, warnings [redirect_would_revert_vendored]
socket-patch scan --mode hosted --yes --ecosystems pypi --json $API
#   exit 1, status partial_failure, redirected 0, warnings
#   [redirect_poetry_lock_unsupported, redirect_takeover_reverted_vendored, redirect_takeover_unpatched]
grep -c socket/vendor poetry.lock; ls .socket/vendor                       # 0; gone
rm -rf .venv && python3 -m venv .venv && VIRTUAL_ENV=$PWD/.venv poetry install   # six installed UNPATCHED

Expected vs actual

The contract does list "a refused lock" among the causes of redirect_takeover_unpatched. But this refusal is whole-lock and deterministic, so it's the same class as the requirements.txt case that the contract already gates before the revert.

Matrix (Linux; scan exits: dry run / wet run; then a fresh install)

Poetry (lock) --dry-run wet takeover fresh install after
0.12.17 ([metadata.hashes]) 0, redirected: 1 1, redirect_takeover_unpatched (reproduced 3×) unpatched
0.12.17, never vendored (plain hosted control) 0, redirect_poetry_lock_unsupported 0, same n/a
1.1.15 (lock 1.1), LF + CRLF – 0, redirected: 1 patched
1.8.5 (lock 2.0), LF + CRLF – 0, redirected: 1 patched
2.5.1 (lock 2.1), LF + CRLF – 0, redirected: 1 patched

The decision is made on lock text, so it doesn't depend on the OS, and I ran no macOS / Windows probe (probe branches are currently blocked for this routine). I didn't bisect. The PyPI takeover exists since #503, and before it the takeover was refused outright (#328).

Suspect code

  • crates/socket-patch-cli/src/commands/scan/hosted.rs:1839-1843: for pkg:pypi/ the takeover_refusal closure runs only preflight_requirements_takeover. There's no Poetry check for a lock the rewriter will refuse.
  • crates/socket-patch-cli/src/commands/scan/hosted.rs:1068: confirmed.extend(dry_run_takeover) counts every previewed takeover as redirected.
  • crates/socket-patch-core/src/utils/poetry_lock.rs:358-359: the lock-version-0 refusal the takeover runs into after reverting.

Related: #723 (uv), #699 / PR #708 (requirements.txt), #853 (pnpm, the hosted → vendored direction).

No activity

Activity on this issue will appear here.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions