Skip to content

Hosted Hatch rewrite leaves an existing Hatch environment unpatched with no stale-install warning, and vex still attests not_affected #335

Description

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

Summary

On a Hatch project whose default environment already exists (the normal state for any developer or warm CI cache), socket-patch scan --mode hosted rewrites six==1.16.0 to six @ <hosted wheel>#sha256=… and reports success with no warnings. But the Hatch environment is never actually patched:

  1. On the next hatch run, Hatch notices the dependency hash changed and runs pip install "six @ <url>". pip downloads the wheel, sees six 1.16.0 is already installed, and keeps the installed bytes (it exits 0 and prints nothing).
  2. Hatch then records the new dependency hash as synced, so later hatch runs never retry. The env stays unpatched until someone runs hatch env remove / hatch env prune.
  3. socket-patch has a redirect_pypi_stale_install check for exactly this situation, but it never sees Hatch's environment. Hatch keeps envs out of tree ($HATCH_DATA_DIR/env/virtual/<project>/<hash>/<env>, or the platform data dir by default), and find_local_venv_site_packages only probes VIRTUAL_ENV, .venv/venv, and the Poetry and Pipenv out-of-tree venvs. The warning fires only when the user happens to scan with VIRTUAL_ENV set to the Hatch env.
  4. socket-patch vex run from the project root emits not_affected for the vulnerability, even though the environment hatch run uses holds the unpatched file. With VIRTUAL_ENV pointing at the Hatch env, the same vex omits it (not_applied), which is correct.

The installer = "uv" flavour is not affected: uv reinstalls the direct-URL wheel. A fresh environment is also fine. The problem is pip-installer Hatch envs that already exist, and that's the default.

Related, same root cause: scan --mode agent on a Hatch project applies 0 patches to the Hatch env (it crawls the PATH interpreter instead) and exits 1 partial_failure. The Poetry siblings of this are #327 and #329.

Impact

  • Users are told the dependency is patched, and a VEX attestation claims not_affected, while hatch run / hatch test / hatch shell keep executing the vulnerable code.
  • Nothing prompts the remedy (hatch env remove <env>, or hatch env prune).

Repro (Linux; macOS and Windows identical, see probe)

Uses a local mock of the patch API (the same shape as crates/socket-patch-cli/tests/vex_pypi_real_common: six 1.16.0 with a SOCKET_PATCHED marker). The full mock and driver are in the probe workflow linked below.

pip install hatch==1.18.1
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.build.targets.wheel]
packages = ["src/app"]
EOF
hatch env create                                  # six 1.16.0 from PyPI
socket-patch scan --mode hosted --json --yes --api-url $API --api-token x --org test-org --patch-server-url $API --ecosystems pypi
#  -> status success, redirected 1, warnings []
hatch run python -c "import six; print(hasattr(six,'SOCKET_PATCHED'))"   # False
hatch run python -c "import six; print(hasattr(six,'SOCKET_PATCHED'))"   # False (hash now recorded as synced)
socket-patch vex --product pkg:pypi/[email protected] --api-url $API --patch-server-url $API --org test-org --api-token x
#  -> 1 statement, status not_affected, "Patched via Socket patch … (redirected)"
VIRTUAL_ENV=$(hatch env find default) socket-patch scan --mode hosted --json --yes …
#  -> warnings: [redirect_pypi_stale_install]   <- only when the env is named explicitly
hatch env remove default
hatch run python -c "import six; print(hasattr(six,'SOCKET_PATCHED'))"   # True

Underlying pip behaviour, inside the Hatch env (pip 26.2.1): pip install "six @ http://…/six-1.16.0-py2.py3-none-any.whl#sha256=…" prints Collecting…/Downloading…, exits 0, and leaves six.py unchanged.

Expected vs actual

  • Expected: consistent with the PyPI stale-install contract in README.md ("Socket Patch warns (redirect_pypi_stale_install …) when a venv still holds the upstream release, with the verified remedy") and with how Poetry and Pipenv out-of-tree venvs are handled in find_local_venv_site_packages. A hosted Hatch scan should find the project's Hatch environments and warn with a Hatch-specific remedy (hatch env remove <env> / hatch env prune, not "reinstall from the rewritten lock", which does nothing here). And vex should not attest not_affected while the Hatch env holds the original bytes.
  • Actual: success, warnings: [], the env stays unpatched indefinitely, and vex attests not_affected.

OS × Hatch version (hosted, pip installer, [project] dependencies)

1.7.0 1.9.7 1.16.5 1.18.1
Linux (sandbox + ubuntu-latest) repro repro repro repro
macOS (macos-latest) repro repro repro repro
Windows (windows-latest) repro repro repro repro

Also reproduced on Linux with [tool.hatch.envs.default] dependencies (env flavour) on 1.16.5. Does not reproduce with installer = "uv" (1.16.5 and 1.18.1: patched on the next hatch run) or on a fresh environment.

First bad

Present since Hatch support landed in 649d457 (#244). No release ships Hatch support yet (v4.0.0 predates it), so there's nothing to bisect.

Suspect code

  • crates/socket-patch-core/src/crawlers/python_crawler.rs:273 find_local_venv_site_packages: no Hatch environment discovery (compare the Poetry and Pipenv arms at :297 and :307).
  • crates/socket-patch-cli/src/commands/scan/hosted/python.rs:57: the stale-install judgment only sees those site-packages. Its generic remedy text (:127) doesn't fit Hatch.
  • VEX's installed-basis lookup uses the same discovery, so it falls back to the pin basis.

Probe

https://github.com/SocketDev/socket-patch/actions/runs/36740025279 (ubuntu, macos and windows × Hatch 1.7.0 / 1.9.7 / 1.16.5 / 1.18.1, all reproduce).

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

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions