Repository navigation
Description
A lookup with an explicit environment uses it for the first interpreter probe but not for the base-interpreter probe. The returned metadata can therefore reflect the process environment instead.
Affected code
src/python_discovery/_py_info.py:474 at the reported commit. The reproducer also returns BUG on current main 004294957ed227fe7e52b2864952ba09fd28b220.
Mechanism
resolve_to_system() invokes the follow-up probe without passing the caller's env mapping.
Reproduction
Check out the reported commit or current main. Save the files below as named, then build and run:
docker build -t python-discovery-audit -f - . <<'DOCKERFILE'
FROM python:3.15-bookworm
RUN python -m venv /opt/venv
ENV PATH=/opt/venv/bin:$PATH
COPY . /src
WORKDIR /src
ENV SETUPTOOLS_SCM_PRETEND_VERSION=1.9.1
RUN pip install -e . --group test
DOCKERFILE
docker run --rm --network=none --memory=2g -u 0 -w /src \
-v "$PWD/repro:/tmp/repro:ro" -v "$PWD/repro.d:/tmp/repro.d:ro" \
python-discovery-audit /tmp/reprorepro
#!/usr/bin/env python3
import json
import os
import subprocess
import tempfile
# set up: a venv of /usr/bin/python3.11; the caller asks for its python by path with
# env={"PATH": "/usr/bin", "PY_DISCOVERY_TIMEOUT": "10"}
root = tempfile.mkdtemp()
venv = os.path.join(root, "venv")
subprocess.run(["/usr/bin/python3.11", "-m", "venv", "--without-pip", venv], check=True)
caller = [
"/opt/venv/bin/python",
"/tmp/repro.d/get_interpreter_with_env_mapping.py",
os.path.join(venv, "bin", "python"),
]
# run 1: PYTHONPATH is in the process environment only, not in the mapping
process_only_dir = os.path.join(root, "only-in-process-environ")
run1 = subprocess.run(caller, env={"PYTHONPATH": process_only_dir}, capture_output=True, text=True, check=True)
info = json.loads(run1.stdout)
print("run 1 executable :", info["executable"])
print("run 1 system_executable:", info["system_executable"])
print("run 1 path :", info["path"])
# run 2: the process environment says 1 ms per probe, the mapping says 10 s
run2 = subprocess.run(caller, env={"PY_DISCOVERY_TIMEOUT": "0.001"}, capture_output=True, text=True)
print("run 2 exit status :", run2.returncode)
print("run 2 last line :", (run2.stdout + run2.stderr).strip().splitlines()[-1])
# check: had the mapping been used for the base probe, run 1's path would not hold
# process_only_dir and run 2 would return the venv like run 1 did
base_probe_got_process_pythonpath = (
info["system_executable"] == "/usr/bin/python3.11" and process_only_dir in info["path"]
)
base_probe_got_process_timeout = "failed to query /usr/bin/python3.11 with code -1 err: 'timed out'" in run2.stderr
print("base probe saw the process PYTHONPATH :", base_probe_got_process_pythonpath)
print("base probe was cut off by the process's 1 ms:", base_probe_got_process_timeout)
print("BUG" if base_probe_got_process_pythonpath and base_probe_got_process_timeout else "OK")repro.d/get_interpreter_with_env_mapping.py
import json
import sys
from python_discovery import get_interpreter
env = {"PATH": "/usr/bin", "PY_DISCOVERY_TIMEOUT": "10"}
info = get_interpreter(sys.argv[1], env=env)
print(json.dumps({"executable": info.executable, "system_executable": info.system_executable, "path": info.path}))Output on 004294957ed227fe7e52b2864952ba09fd28b220:
run 1 executable : /tmp/tmp7511fxsr/venv/bin/python
run 1 system_executable: /usr/bin/python3.11
run 1 path : ['/src/src/python_discovery', '/tmp/tmp7511fxsr/only-in-process-environ', '/usr/lib/python311.zip', '/usr/lib/python3.11', '/usr/lib/python3.11/lib-dynload', '/usr/local/lib/python3.11/dist-packages', '/usr/lib/python3/dist-packages']
run 2 exit status : 1
run 2 last line : RuntimeError: failed to query /usr/bin/python3.11 with code -1 err: 'timed out'
base probe saw the process PYTHONPATH : True
base probe was cut off by the process's 1 ms: True
BUG
Expected: Both probes should use the supplied mapping, including its timeout setting.
Impact
Reproducibility is moderate because the setup requires a specific candidate or configuration. Exposure is niche: the candidate must be a venv or symlink and the supplied mapping must differ from os.environ. The reproduced effect stays within the caller's configured cache, search path, or interpreter.
Credits
Reported by Anthropic's OSS Scanner: ANT-2026-HAL1EPEA.
Activity