Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
6351f83
Support Poetry patch lock generations
mikolalysenko Sep 17, 2026
c02770c
Keep Poetry rollback source and hashes together
mikolalysenko Sep 17, 2026
2ac2436
Keep Poetry fixtures portable across checkouts
mikolalysenko Sep 17, 2026
02c63ec
fix(poetry): harden the shared lock rewriter and align hosted refusal…
mikolalysenko Sep 17, 2026
66680d8
fix(poetry): key the vendored pre-1.4 advisory on the lock's writer
mikolalysenko Sep 17, 2026
98a3253
test(poetry): add the per-release live matrix harness and its results
mikolalysenko Sep 17, 2026
64c7a29
docs(poetry): document measured installer boundaries, warning codes a…
mikolalysenko Sep 17, 2026
e72e679
fix(pypi): discover Poetry's out-of-tree virtualenv in agent mode
mikolalysenko Sep 17, 2026
e44a534
fix(pypi): vendor Poetry projects from a lock-only checkout
mikolalysenko Sep 17, 2026
b7fbd98
docs(poetry): describe automatic virtualenv discovery and lock-only v…
mikolalysenko Sep 17, 2026
6803e13
fix(poetry): verify lock-1.0 hosted wheels on Poetry >= 1.2 and name …
mikolalysenko Sep 17, 2026
b7a4254
fix(vex): attest same-run hosted redirects whose ledger purl carries …
mikolalysenko Sep 17, 2026
08030bd
fix(vendor): let only fragment drift hold the atomic lock revert
mikolalysenko Sep 17, 2026
e419c42
test(poetry): regenerate the matrix results on the fixed CLI
mikolalysenko Sep 17, 2026
e58c40a
fix(poetry): keep hosted rollback invertible across Poetry's own relocks
mikolalysenko Sep 17, 2026
75d49ac
test(poetry): exercise relock → re-scan → rollback in the live matrix
mikolalysenko Sep 17, 2026
806fe3f
test(poetry): final matrix results on the fixed CLI; isolate HOME for…
mikolalysenko Sep 17, 2026
eccfe2c
Fix Poetry CI line endings and stale hosted attestations
mikolalysenko Sep 17, 2026
106645c
Retry Windows delete-pending race in apply lock test
mikolalysenko Sep 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,38 @@ into the new version's section — see docs/releasing.md.

### Added

- **Poetry projects take hosted patches, and vendored patches now cover every
`poetry.lock` generation.** `scan --mode hosted` rewrites `poetry.lock` to a
`[package.source] type = "url"` pointing at the Socket-hosted, SHA-256-pinned
wheel (Poetry 1.0 through 2.x; Poetry 0.12 ignores URL sources and is refused
with `redirect_poetry_lock_unsupported`), and `scan --mode vendored` accepts
the legacy `[metadata.hashes]` (0.12) and `[metadata.files]` (lock 1.0/1.1)
layouts next to the 2.x `files` arrays, CRLF locks included. The rewrite keeps
every other byte of the lock — dependency metadata, groups, markers, extras
and the pyproject `content-hash` — and records independent rollback fragments
per patch, so `rollback` restores the recorded originals in any order.
Verified end-to-end against real Poetry 0.12.17, 1.0.10, 1.1.15, 1.2.2,
1.3.2, 1.4.2, 1.5.1, 1.6.1, 1.7.1, 1.8.5, 2.0.1, 2.1.4, 2.2.1, 2.3.4 and
2.4.3 in hosted, vendored and agent mode — see
`docs/testing/poetry-compatibility.md` and `scripts/backtest-poetry.py`.
Poetry releases before 1.4 neither verify local wheel hashes nor replace an
already-installed package at the same version; both modes surface that as an
advisory (`pypi_poetry_integrity_unverified`, `redirect_poetry_stale_install_risk`)
keyed on the lock's writer, and the hosted rewriter warns
`redirect_poetry_entry_not_found` when a lock has no entry for a granted
patch (uv parity). A rotated grant token or republished patch supersedes the
earlier hosted URL in place instead of being refused as a foreign source,
a future `lock-version = "2.<n>"` is rewritten like 2.1 on every path
(the vendored loader already accepted it), and a malformed
`[metadata.files]` / `[metadata.hashes]` value is refused instead of
panicking the scan. Rollback stays invertible across Poetry's own relocks:
the recorded package fragment carries its boundary header, so a unit that
Poetry 1.1/1.2 re-laid (source kept, inserted `files` line dropped) is
refused rather than mistaken for an already-reverted lock, a lock-1.0
redirect restored by hand converges instead of refusing, and a re-scan
after such a relock REBASES the ledger's edits (pristine → current)
instead of appending a chain whose older links match nothing — which made
`rollback` and `remove` refuse forever. (#241)
- **Python patches survive uv lockfiles in both hosted and vendored modes.**
`scan --mode hosted|vendored` now rewrites native `uv.lock` together with
the paired `pyproject.toml` source and metadata, PEP 723 script locks
Expand Down Expand Up @@ -168,6 +200,32 @@ into the new version's section — see docs/releasing.md.

### Fixed

- Hosted Python redirects now warn when installed files still contain upstream
or modified bytes and omit those packages from same-run VEX. The read-only
probe covers Poetry virtualenvs, repeats on re-scans, and uses persisted patch
records if fetching fresh records fails.

- **Agent mode finds Poetry's out-of-tree virtualenv.** Poetry keeps a
project's virtualenv under `{cache-dir}/virtualenvs/<name>-<hash>-py<X.Y>`
by default, so after a plain `poetry install` the crawler saw no
`VIRTUAL_ENV` / `.venv` / `venv` and fell through to the global interpreter:
`scan --mode agent` patched nothing for the project's dependencies (or the
wrong interpreter) while reporting success, and a bare `rollback` pruned the
manifest while the venv stayed patched. The crawler now reproduces Poetry's
own placement — `virtualenvs.create` / `in-project` / `path` and `cache-dir`
from `POETRY_*`, the project's `poetry.toml` and the user `config.toml`, the
platform default cache dir, and Poetry's env-name hash — without running
Poetry, and scans every `-py<X.Y>` sibling. `poetry run socket-patch …` and
`VIRTUAL_ENV` keep working as before.
- **`scan --mode vendored` works from a lock-only Poetry checkout.** The
`poetry.lock` inventory was discovery-only, so a fresh clone with nothing
installed was skipped with `vendor_fetch_unverifiable` even though the lock
records the wheel's sha256 (uv's lock vendored fine in the same scenario).
The inventory now carries the pure-Python wheel's sha256 from `files` (lock
2.x) or `[metadata.files]` (lock 1.0/1.1), and the pypi fetcher resolves a
hash-only entry through PyPI's JSON API by that digest (verified again after
download; `SOCKET_PYPI_JSON_API` overrides the endpoint). Poetry 0.12's bare
`[metadata.hashes]` names no wheel and still needs an installed copy.
- **`remove` no longer drops the manifest entry of a drift-kept vendored
purl.** When the vendored revert keeps the artifact (`kept_artifact` —
the lockfile drifted), the manifest entry is now kept too
Expand Down
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ The same patched bytes can reach your build three different ways. The modes diff
|------|----------------------|--------------------------|-----------|
| **agent** — `scan --mode agent` (or [`apply`](#apply)) | `.socket/` manifest + blobs, committed; the CLI re-applies after each install | The `socket-patch` CLI must run (install hook via [`setup`](#setup), or an `apply` step in CI) | Small repo footprint (per-file blobs, not whole packages); no lockfile edits; the only mode that needs CI / install-hook changes |
| **vendored** — `scan --mode vendored` (or [`vendor`](#vendor)) | Patched packages committed under `.socket/vendor/`; the lockfile is rewired to consume them | **None** — the package manager installs the committed bytes | Fully airgapped and hermetic, at the cost of repo size |
| **hosted** — `scan --mode hosted` | No patched bytes in your repo: the lockfile is rewritten so **only** the patched dependencies resolve to Socket-hosted, integrity-pinned packages on `patch.socket.dev`; the edits + patch records are ledgered in `.socket/vendor/redirect-state.json` (commit it — [`vex`](#vex) reads it, and it records the pre-redirect originals a future revert feature will need; hosted has no CLI revert yet, see [Undo things](#undo-things)) | Installs must be able to reach `patch.socket.dev` (no CLI, no install hook) | Smallest possible diff (lockfile + ledger); not for airgapped installs |
| **hosted** — `scan --mode hosted` | No patched bytes in your repo: the lockfile is rewritten so **only** the patched dependencies resolve to Socket-hosted, integrity-pinned packages on `patch.socket.dev`; the edits + patch records are ledgered in `.socket/vendor/redirect-state.json` (commit it — [`vex`](#vex) reads it, and [`rollback`](#rollback) replays its recorded pre-redirect originals to unwind the redirect, see [Undo things](#undo-things)) | Installs must be able to reach `patch.socket.dev` (no CLI, no install hook) | Smallest possible diff (lockfile + ledger); not for airgapped installs |

Every mode pins the patched bytes: in agent mode the CLI verifies every file on each
apply; vendored and hosted modes lean on your package manager's own lockfile integrity
Expand Down Expand Up @@ -356,11 +356,12 @@ and repair; pick by what you want back:

And `setup --remove` reverts the install hooks that `setup` added.

> Hosted mode has no CLI revert yet: `scan --mode hosted` makes plain lockfile /
> registry-config edits, so undo them with your version control (e.g.
> `git checkout -- <lockfile>`) and delete the `.socket/vendor/redirect-state.json`
> ledger — once you've reverted by hand, its recorded original fragments are stale, and
> a leftover ledger would still let [`vex`](#vex) attest the removed redirects.
> Hosted mode is unwound by [`rollback`](#rollback), which replays the original
> lockfile / registry-config fragments recorded in `.socket/vendor/redirect-state.json`
> and drops the redirect records. If you revert a hosted edit by hand instead (e.g.
> `git checkout -- <lockfile>`), also delete that ledger — its recorded originals are
> then stale, and a leftover ledger would still let [`vex`](#vex) attest the removed
> redirects.

## Command reference

Expand Down
Loading
Loading