Repository navigation
Hosted Hatch scan exits 0 "redirected" when [tool.uv.sources] pins the patched package, but Hatch's uv installer obeys the source: url/path sources install the unpatched wheel and git sources break every install #673
Description
[agent] Found by the scheduled Hatch bug-hunt routine (ledger #314).
Summary
In a Hatch project whose environment uses installer = "uv", a [tool.uv.sources] entry for the patched package (for example a url, path or git source) takes precedence over the PEP 508 direct reference that hosted mode writes into [project].dependencies. Hatch installs the project with uv pip install -e ., and uv applies the project's tool.uv.sources. Hosted scan never looks at [tool.uv.sources]. It rewrites six==1.16.0 to six @ <patch url>#sha256=… and reports success, redirected: 1, exit 0. Then:
- with a
urlorpathsource, every fresh Hatch env silently installs the unpatched upstream wheel; - with a
gitsource, every Hatch env creation fails withConflicting URLs for package six.
The docs say a source table is refused before anything is written, so the scan should have failed closed.
Impact
CI gets a green scan and a pyproject that looks patched, but the vulnerable code still ships. For git sources the project stops installing at all. vex does correctly omit six (not_applied) when it inspects the env, so the silent part is the scan result. Vendored mode on the same project refuses (with pypi_uv_no_lockfile), so it fails closed.
Repro (Hatch 1.18.1, uv 0.12, Linux; local mock patch API serving a patched six 1.16.0 wheel)
mkdir app && cd app && mkdir -p src/app && touch src/app/__init__.py
cat > pyproject.toml <<'EOF'
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "app"
version = "0.1.0"
dependencies = ["six==1.16.0"]
[tool.hatch.envs.default]
installer = "uv"
[tool.uv.sources]
six = { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl" }
EOF
socket-patch scan --mode hosted --yes --json --ecosystems pypi … # exit 0, status success, redirect.redirected = 1, no warnings
grep dependencies pyproject.toml # six @ http://…/patch/pypi/six/1.16.0/…/six-1.16.0-py2.py3-none-any.whl#sha256=…
hatch run python -c 'import six; print(getattr(six, "SOCKET_PATCHED", "UNPATCHED"))' # fresh env -> UNPATCHEDSwap the source for { path = "/abs/six-1.16.0-py2.py3-none-any.whl" } to get the same result. With { git = "https://github.com/benjaminp/six", tag = "1.16.0" }, hatch run fails with:
error: Requirements contain conflicting URLs for package `six`:
- git+https://github.com/benjaminp/[email protected]
- http://127.0.0.1:18080/patch/pypi/six/1.16.0/…/six-1.16.0-py2.py3-none-any.whl
Expected vs actual
- Expected: docs/testing/hatch.md: "A range, transitive-only declaration, dynamic dependency metadata, custom environment plugin, source table or conditional override is refused before writing. Use agent mode…". A uv source for a package Hatch installs through uv should be refused (or at least warned about) the same way Hatch
sourcesare, with nothing written and a non-zero or warning result. - Actual: exit 0
success,redirected: 1, no warning. The rewrite is either ignored (url/path) or breaks installs (git).
Matrix (Linux, each cell run twice)
| Shape | Hatch 1.16.5 | Hatch 1.18.1 |
|---|---|---|
installer = "uv", project deps, tool.uv.sources url |
fail (unpatched) | fail (unpatched) |
installer = "uv", project deps, tool.uv.sources path |
fail (unpatched) | fail (unpatched) |
installer = "uv", project deps, tool.uv.sources git |
fail (install error) | fail (install error) |
installer = "uv", features = ["x"] (optional deps), url source |
— | fail (unpatched) |
installer = "uv", env dependencies, url source |
— | pass (env reqs are passed to uv as plain strings, so project sources don't apply) |
installer = "uv", tool.uv.sources index source (explicit index) |
— | pass (the direct URL wins) |
| pip installer (default), url source | — | pass (pip ignores tool.uv.sources) |
macOS / Windows: not probed (probe branches can't be deleted from this sandbox right now). The rewrite is OS-independent.
Suspect code
crates/socket-patch-core/src/utils/hatch.rs:247 refuses only Hatch's own tool.hatch.sources / env. plan() (utils/hatch.rs:344) reads installer == "uv" for the env but never consults tool.uv.sources, so rewrite_project (utils/hatch.rs:187) rewrites a dependency that uv then resolves from the source. A fix could refuse (or warn and skip) any patched name that appears in [tool.uv.sources] when an env that installs the project uses the uv installer.
Related but different: #564 (uv lane, no-sources = true).
Backlog review — 2026-10-08
Priority: P1 → P2. Hatch/uv URL or path overrides can keep installing upstream bytes despite the hosted scan; git overrides cause an install failure. VEX rejects the mismatched environment. Retain this real source-selection defect at P2.
Activity
mikolalysenko commentedon Oct 3, 2026
[agent] Triage: priority:p1 (Hatch, PyPI family). Confirmed on main
045d7ec:crates/socket-patch-core/src/utils/hatch.rs:247refuses only Hatch's ownsources/envtables, and nothing inhatch.rsreads[tool.uv.sources]. So hosted mode rewrites a dependency that the uv installer then resolves from the source. I found no duplicate. #564 (the uv lane'sno-sources) is related but has a different code path. This isn't fixed on main and no PR is open for it.Generated by Claude Code
mikolalysenko commentedon Oct 24, 2026
[agent] Claiming this issue (shared root cause: hosted Hatch planner never consults
[tool.uv.sources], which Hatch's uv installer applies over the rewritten project dependency). Branch: agent/fix-hatch-uv-sources-pin. Claim-ID: 2026-10-24T02:20:54Z-9bb9f1Generated by Claude Code
mikolalysenko commentedon Oct 24, 2026
[agent] Draft PR: #1706
Generated by Claude Code
Refuse hosted Hatch pins shadowed by uv sources
Also refuse uv sources under the uv env switch
mikolalysenko commentedon Oct 24, 2026
[agent] #1706 is ready for review. CI is green on
84d95a4and Bugbot found no issues. Hosted Hatch now refuses, before writing, a project-table pin of a package that has a url, path or git[tool.uv.sources]entry when an environment uses the uv installer. It reportsredirect_hatch_unsupportedinstead of a falseredirected. Environment dependencies, index sources and the pip installer still rewrite, matching the matrix in this issue.Generated by Claude Code