py_binary: ambient `PYTHONPATH` shadows runfiles packages under `bazel run`
Maintainer antworten meist innerhalb von 1 Tag
Dieses Issue hat noch niemand übernommen.
Bewertung
- Schwierigkeit
- 4/5
- Geschätzter Aufwand
- 3-5 Tage
- Anfängerfreundlichkeit
- 55/100
- Issue-Typ
- Bug
- Klarheit
- Größtenteils klar
- Aktivitätsstatus
- Ruhig
- Tech-Stack
- python
- Bereich
- build-system, tooling
Rechercherichtung
Beginne mit den Zeilen 263-275 in python/private/stage1_bootstrap_template.sh und den gemeinsamen stage-2-bootstrap-Kommentaren und reproduziere dann den PYTHONPATH-Fall mit führendem Doppelpunkt mit bazel run. Vergleiche das Launcher-Verhalten mit den -I-Aufrufen in toolchains_repo.bzl, whl_library.bzl und runtime_env_repo.bzl. Die Aufgabe ist erledigt, wenn die Reproduktion nicht mehr zulässt, dass ein vorhandener PYTHONPATH die runfiles überschreibt, und das Verhalten von py_binary/py_test sowie die Hinweise zu Umgebungsvariablen abgedeckt sind.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
🐞 bug report
Affected Rule
py_binary (via bazel run), for both --bootstrap_impl=script and --bootstrap_impl=system_python.
The fault lives in the shared py_binary/py_test stage1/stage2 launcher, so py_test is affected in principle too, but only when a hostile PYTHONPATH is explicitly forwarded with --test_env, and a default bazel test does not trip it (see Scope below).
Is this a regression?
No.
This has been the behavior since PYTHONSAFEPATH was adopted as the launcher's only sys.path mitigation (the fix for #382).
Ambient PYTHONPATH shadowing was never addressed.
Description
An ambient PYTHONPATH in the environment that invokes bazel run is honored by the launched interpreter and prepended to sys.path ahead of the program's own runfiles.
When a directory on that path shares a top-level package name with a runfiles package, it shadows it as a PEP 420 namespace package, and the program fails to import its own declared dependencies.
A target thus resolves imports against the developer's shell state instead of its declared deps, which contradicts Bazel's "depend only on declared inputs" contract.
This is especially easy to trigger because a leading (or trailing, or doubled) : in PYTHONPATH is interpreted by CPython as the current working directory, and under bazel run the cwd is the workspace root, which routinely contains directories whose names collide with common top-level packages (pkg, lib, src, etc.).
Concretely, this breaks rules_pkg's install script.
It is enough for a developer's PYTHONPATH to merely begin with a colon (e.g. :/home/dev/another-project/src, which is what happens when some unrelated tool prepends an entry): that leading empty entry resolves to the current working directory, which under bazel run is the workspace root.
The workspace root may contain a top-level pkg/ directory, a very common monorepo layout (Go uses it by convention, but it is widespread beyond Go and has nothing to do with Python here).
Because the leading colon placed the cwd on sys.path ahead of the runfiles, import pkg binds to that pkg/ as a namespace package, and pkg.private (shipped by rules_pkg) is no longer importable (see the traceback below).
Root cause
The stage-1 launcher sets PYTHONSAFEPATH=1, which removes the auto-prepended sys.path[0] (the script directory / cwd), the fix for #382, but -P / PYTHONSAFEPATH does not stop CPython from honoring PYTHONPATH entries:
https://github.com/bazel-contrib/rules_python/blob/e7d137877d4fba2cb58cd4188b7af480e211ef5b/python/private/stage1_bootstrap_template.sh#L263-L275
The asymmetry
rules_python already runs its own interpreter invocations in isolated mode (-I, which implies -E -P -s) precisely so that userspace variables such as PYTHONPATH cannot interfere:
https://github.com/bazel-contrib/rules_python/blob/e7d137877d4fba2cb58cd4188b7af480e211ef5b/python/private/toolchains_repo.bzl#L371-L374
https://github.com/bazel-contrib/rules_python/blob/e7d137877d4fba2cb58cd4188b7af480e211ef5b/python/private/pypi/whl_library.bzl#L121-L124
https://github.com/bazel-contrib/rules_python/blob/e7d137877d4fba2cb58cd4188b7af480e211ef5b/python/private/runtime_env_repo.bzl#L23-L30
User binaries get only -P, and the gap is not yet documented, so a user has no way to discover that bazel run may quietly resolve imports against their shell environment instead of the target's declared deps.
Scope: bazel run vs bazel test
py_binary and py_test share the same stage1/stage2 launcher, so the flaw is in principle common to both.
In practice it surfaces under bazel run but not under a default bazel test, for two independent reasons:
bazel runinherits the full client environment, so the ambientPYTHONPATHreaches the interpreter asbazel testdoes not propagate it by default (the test process seesPYTHONPATH=Noneunless--test_env=PYTHONPATHis passed),bazel run's cwd is the workspace root (so a leading-:empty entry resolves to it, where the straypkg/lives), whereas a test's cwd is its runfiles tree, i.e. the correct layout.
A py_test can still be made to fail by explicitly forwarding a hostile PYTHONPATH via --test_env=PYTHONPATH=/dir/containing/pkg (the entry does land in sys.path ahead of the runfiles), so the launcher fix matters for both, even though only bazel run trips it implicitly.
Potential resolution
- make runfiles win over ambient
PYTHONPATH: ordersys.pathin the stage-2 bootstrap so the program's runfiles / venv site-packages precede any ambientPYTHONPATHentries.
This kills the shadowing of a target's declared dependencies without changing how much of the environment the interpreter otherwise honors, and without propagating anything to child processes, so it sidesteps the tension in #2060 / #2122 (where users wanted less stickiness, not more).
This would be a minimal correctness fix. - discoverable documentation, e.g. a note in the bootstrap / environment-variables docs explaining that
scriptandsystem_pythonlaunchers honor ambientPYTHON*variables (notablyPYTHONPATH), that this can shadow a target's runfiles, and that the interpreter can be hardened viainterpreter_args = ["-I"]orRULES_PYTHON_ADDITIONAL_INTERPRETER_ARGS=-I.
Thestage2header comment already documents thesys.path[0]/PYTHONSAFEPATHstory but is silent onPYTHONPATH.
The strongest alternative would be to default the launcher to -I instead of -P, converging on Bazel's hermeticity expectation, but one has to keep in mind that -I implies -E, thus ignoring all PYTHON* variables, which conflicts with the direction of #2060 / #2122.
A default flip would also need a real opt-out:
- the existing
RULES_PYTHON_ADDITIONAL_INTERPRETER_ARGSis additive, so an empty value cannot cancel a hardcoded-I, - the variable would have to become an override of the default isolation args, which is itself a breaking change.
🔬 Minimal Reproduction
- A
py_binarywhose runfiles contain a top-level packagefoo(with a submodulefoo.bar). - A directory named
foo/in the workspace root that is not that package (e.g. a non-Python directory, or afoo/withoutbar.py). - Run with an empty
PYTHONPATHentry:PYTHONPATH=: bazel run //path/to:app - Observe
ModuleNotFoundError: No module named 'foo.bar': the workspace-rootfoo/shadows the runfilesfoo/.
Clearing the variable (PYTHONPATH= bazel run ...) or isolating the interpreter (RULES_PYTHON_ADDITIONAL_INTERPRETER_ARGS=-I) makes it pass.
🔥 Exception or Error
INFO: Running command line: bazel-bin/pkg/sth/install <args omitted>
Traceback (most recent call last):
...
File ".../install.runfiles/_main/pkg/sth/install_install_script.py", line 29, in <module>
from pkg.private import manifest
ModuleNotFoundError: No module named 'pkg.private'
🌍 Your Environment
Operating System:
Linux
macOS
Windows
Output of bazel version:
Bazelisk version: development
Build label: 9.1.1
Build target: @@//src/main/java/com/google/devtools/build/lib/bazel:BazelServer
Build time: Wed Jun 03 15:41:13 2026 (1780501273)
Build timestamp: 1780501273
Build timestamp as int: 1780501273
Rules_python version:
2.0.3
Anything else relevant?
Prior art:
- #382 (
sys.path[0], fixed viaPYTHONSAFEPATH), - #2060 (opt-out of
PYTHONSAFEPATH), - #2122 (closed PR moving env to
-P), - #3437 (subprocess module resolution under
system_python).
- Vorherrschende Sprache
- Starlark
- Sterne
- 687
- Forks
- 726
- Ø Merge
- 1 T. 3 Std.
- Gemergte PRs (30 T.)
- 83
Entwicklungsumgebung
- Kein Dockerfile und keine Docker-Compose-Datei
- Hat eine Pull-Request-Vorlage
- Beitragsleitfaden lesen
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus bazel-contrib/rules_python
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 68/100
bazel-contrib/rules_python#4251 · 1 Reaktion ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
bazel-contrib/rules_python#4164 · 1 Kommentar ·
Maintainer antworten meist innerhalb von 1 Tag
-
go:embed stdlib_list.txt file in gazelle/python/std_modules.go is missingEvtl. vergeben @udaya2899 hat das vor 20 Tagen übernommen. Offen
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 72/100
bazel-contrib/rules_python#3821 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 4/5 3-5 Tage Anfängerfreundlichkeit 38/100
bazel-contrib/rules_python#4278 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 3/5 1-2 Tage Anfängerfreundlichkeit 45/100
bazel-contrib/rules_python#4269 ·
Maintainer antworten meist innerhalb von 1 Tag
Alle Issues in bazel-contrib/rules_python
Ähnliche Issues
-
maintenance
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 75/100
alphagov/accessible-autocomplete#826 ·
Maintainer antworten meist innerhalb von 4 Tagen
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 77/100
olahol/react-tagsinput#229 ·
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 86/100
google-ai-edge/litert-torch#1346 ·
Maintainer antworten meist innerhalb von 1 Tag
-
status:waiting-for-triage type:bug
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 84/100
microsoftgraph/msgraph-sdk-dotnet#3194 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 75/100
Maintainer antworten meist innerhalb von 1 Tag