What happened?
salt-pip is meant to be a self-contained wrapper around the onedir's bundled pip: packages it installs go into an isolated extras-. directory alongside the onedir, kept separate from the system Python (see doc/topics/packaging/index.rst). In practice, that isolation is incomplete.
_pip_environment() in salt/scripts.py builds the environment for the pip subprocess like this:
def _pip_environment(env, extras):
new_env = env.copy()
if "PYTHONPATH" in env:
new_env["PYTHONPATH"] = f"{extras}{os.pathsep}{env['PYTHONPATH']}"
else:
new_env["PYTHONPATH"] = extras
return new_env
Rather than isolating the subprocess, it prepends salt's own extras directory onto whatever PYTHONPATH was already present in the calling process's environment. If anything invoking salt-pip — a shell session, an init/systemd unit, a packaging post-install hook, a CI job, etc. — has a PYTHONPATH set (intentionally or via leakage from unrelated tooling), that path is carried straight through into salt's supposedly isolated Python subprocess.
Why this is a problem
PYTHONPATH entries are visible to whichever interpreter is running, regardless of which Python installation they actually belong to — a pure-Python package sitting in an unrelated Python 3.x installation's site-packages is just as importable from salt's bundled/relenv interpreter as one installed properly inside extras.
This becomes actively destructive with --force-reinstall: pip's install logic uninstalls whatever it finds "already satisfying" a given requirement anywhere on sys.path, not just inside the directory it's about to install into. So salt-pip install --force-reinstall <pkg> can end up deleting a same-named package belonging to a completely unrelated Python environment — the host's system Python, or some other application's separate Python stack — if that environment happens to be exposed via an inherited PYTHONPATH. That's a package the user never asked salt-pip to touch, disappearing from a Python installation salt-pip has no business modifying, potentially breaking whatever depended on it.
Impact
Observed in the field: a PYTHONPATH leaked into the environment from an unrelated build/init process caused salt-pip install --force-reinstall to delete a package belonging to a separate, unrelated Python 3.x application stack on the same host, breaking that application.
Expected behavior
salt-pip's subprocess environment should be genuinely isolated, as documented: it should only ever expose its own managed extras directory on PYTHONPATH, never merge in anything inherited from the calling process's environment.
Type of salt install
Official deb
Major version
3006.x
What supported OS are you seeing the problem on? Can select multiple. (If bug appears on an unsupported OS, please open a GitHub Discussion instead)
debian-11
salt --versions-report output
What happened?
salt-pipis meant to be a self-contained wrapper around the onedir's bundled pip: packages it installs go into an isolated extras-. directory alongside the onedir, kept separate from the system Python (see doc/topics/packaging/index.rst). In practice, that isolation is incomplete._pip_environment()insalt/scripts.pybuilds the environment for the pip subprocess like this:Rather than isolating the subprocess, it prepends salt's own
extrasdirectory onto whateverPYTHONPATHwas already present in the calling process's environment. If anything invokingsalt-pip— a shell session, an init/systemd unit, a packaging post-install hook, a CI job, etc. — has aPYTHONPATHset (intentionally or via leakage from unrelated tooling), that path is carried straight through into salt's supposedly isolated Python subprocess.Why this is a problem
PYTHONPATHentries are visible to whichever interpreter is running, regardless of which Python installation they actually belong to — a pure-Python package sitting in an unrelated Python 3.x installation'ssite-packagesis just as importable from salt's bundled/relenv interpreter as one installed properly inside extras.This becomes actively destructive with
--force-reinstall: pip's install logic uninstalls whatever it finds "already satisfying" a given requirement anywhere onsys.path, not just inside the directory it's about to install into. Sosalt-pip install --force-reinstall <pkg>can end up deleting a same-named package belonging to a completely unrelated Python environment — the host's system Python, or some other application's separate Python stack — if that environment happens to be exposed via an inheritedPYTHONPATH. That's a package the user never askedsalt-pipto touch, disappearing from a Python installationsalt-piphas no business modifying, potentially breaking whatever depended on it.Impact
Observed in the field: a
PYTHONPATHleaked into the environment from an unrelated build/init process causedsalt-pip install --force-reinstallto delete a package belonging to a separate, unrelated Python 3.x application stack on the same host, breaking that application.Expected behavior
salt-pip's subprocess environment should be genuinely isolated, as documented: it should only ever expose its own managed extras directory onPYTHONPATH, never merge in anything inherited from the calling process's environment.Type of salt install
Official deb
Major version
3006.x
What supported OS are you seeing the problem on? Can select multiple. (If bug appears on an unsupported OS, please open a GitHub Discussion instead)
debian-11
salt --versions-report output