CycloneDX / CycloneDX/cyclonedx-python
environment subcommand: inherited PYTHONPATH pollutes target venv's sys.path
- Dominant language
- Python
- Stars
- 390
- Forks
- 98
- Avg merge
- 2d 14h
- Merged PRs (30d)
- 1
Description
## Summary
When `cyclonedx-py environment ` is invoked from a process that has
`PYTHONPATH` set, the `PYTHONPATH` is inherited by the subprocess that
`cyclonedx-py` spawns to enumerate `sys.path` of the target interpreter. As a
result, packages from outside the target venv appear in the generated SBOM -
packages that are not installed in the environment being scanned at all.
## Root cause (probably)
In cyclonedx_py/_internal/environment.py, the __path4python method spawns a
subprocess to read the target interpreter's `sys.path`:
```python
# cyclonedx_py/_internal/environment.py, __path4python()
res = run(cmd, capture_output=True, encoding='utf8', shell=False) # nosec
```
`subprocess.run` is called without an explicit env= argument, so it inherits the
full environment of the calling process including `PYTHONPATH`. When
`PYTHONPATH` is set in the caller (e.g. by a build tool, test runner, or task
runner that places its own packages there), those paths are prepended to
`sys.path` inside the spawned interpreter. The resulting path list is then used
by `importlib.metadata.distributions(path=...)` to discover packages, so any
package reachable via the inherited `PYTHONPATH` is included in the SBOM as if
it were installed in the target venv.
## Minimal reproducible example
```python
uv tool install cyclonedx-bom
# Create the venv to be scanned (install only 'requests')
uv venv /tmp/scan_venv
uv pip install --python=/tmp/scan_venv/bin/python requests
# Create a separate directory with an unrelated package visible via PYTHONPATH
uv venv /tmp/outer_venv
uv pip install --python=/tmp/outer_venv/bin/python flask
# Run cyclonedx-py with PYTHONPATH pointing at the outer package directory
PYTHONPATH=$(/tmp/outer_venv/bin/python -c "import sysconfig; print(sysconfig.get_path('purelib'))") \
cyclonedx-py environment /tmp/scan_venv -o /tmp/sbom.json
# Inspect the result -> 'Flask' (and its dependencies) appear in the SBOM
# even though it is NOT installed in /tmp/scan_venv
uv run python -c "
import json
data = json.load(open('/tmp/sbom.json'))
names = [c['name'] for c in data.get('components', [])]
print('flask in SBOM:', 'Flask' in names)
print('All components:', sorted(names))
"
```
Expected: SBOM contains only requests (and its dependencies).
Actual: SBOM also contains flask (and its dependencies), which were never
installed in the scanned venv.
## Why it matters
Any tool that invokes cyclonedx-py in-process or as a subprocess after setting
PYTHONPATH including build systems, task runners, and CI pipelines that add
their own directories to PYTHONPATH will silently produce an inaccurate
SBOM. The SBOM over-reports dependencies that are not actually present in the
scanned environment, which undermines the entire purpose of SBOM generation for
supply-chain auditing and compliance.
## Suggested fix
Strip PYTHONPATH (and, for robustness, VIRTUAL_ENV and PYTHONHOME) from the
environment passed to the subprocess in `__path4python`:
```python
import os
def __path4python(self, python: str, import_site: bool) -> list[str]:
cmd = [self.__py_interpreter(python),
'-c', 'import json,sys;json.dump(sys.path,sys.stdout)']
if not import_site:
cmd.insert(1, '-S')
# Explicitly clear env vars that would pollute the target interpreter's
# sys.path and cause packages from outside the target venv to appear in
# the discovered distributions.
clean_env = {
k: v for k, v in os.environ.items()
if k not in ('PYTHONPATH', 'PYTHONHOME', 'VIRTUAL_ENV')
}
self._logger.debug('fetch `path` from python interpreter cmd: %r', cmd)
res = run(cmd, capture_output=True, encoding='utf8', shell=False, env=clean_env)
...
```
## Environment
- Package version: cyclonedx-bom 7.3.0
- Python version: 3.10+
- Platform: Ubuntu 24.04
## Contribution
- [x] I am willing to provide a fix
- [ ] I will wait until somebody else fixes it
Contributor guide
Assessment
This issue has not been assessed yet.