GHSA-xmj3-9gf7-h52w
setuptools: Windows path escape via namespace_packages.txt during legacy wheel installation Description
GHSA-xmj3-9gf7-h52w is a low vulnerability (CVSS 1.0) in setuptools, published October 23, 2026. No source lists public exploit code. Fixed in setuptools 84.1.0.
- Severity
- 1.0 LowCVSS 4.0, GitHub Advisories
- Exploit chance
- UnscoredNo EPSS score yet
- Exploitation
- None knownNot on CISA’s or ENISA’s lists
- Fixed in
84.1.0setuptools +5 more
Description
Summary
On Windows, attacker-controlled content in a wheel's
.dist-info/namespace_packages.txt can cause setuptools' legacy wheel
installation path to create an __init__.py outside the requested
destination_eggdir.
The attacker can influence the destination path, but not the contents of the created file.
Details
Tested setuptools commit:
6ead555c5fb29bc57fe6105b1bffc163f56fd558
Relevant source:
setuptools/wheel.py
SHA-256:
359b7e0d5dd8c3135653abe08a8ed5dc0352eadfd7df22cb5e2b5303b7c3cafd
The relevant call path is:
Wheel(wheel_path).install_as_egg(destination)
→ _install_as_egg
→ _convert_metadata
→ _fix_namespace_packages
A wheel can provide attacker-controlled content in:
<distribution>.dist-info/namespace_packages.txt
The namespace value is eventually used approximately as:
mod_dir = os.path.join(destination_eggdir, *mod.split('.'))
mod_init = os.path.join(mod_dir, '__init__.py')
If mod is a Windows drive-qualified absolute path containing no dots,
mod.split('.') preserves it as a single component.
On Windows, os.path.join(destination_eggdir, absolute_component) discards
the original base directory and resolves to the attacker-supplied absolute
path.
The existing wheel filename and ZIP traversal checks do not validate this
metadata value before it is subsequently interpreted as a filesystem path.
```markdown
### PoC
The reproduction was performed entirely inside a disposable temporary
directory.
Layout:
```text
sandbox/
destination/
outside/
input/
canary.txt
A synthetic wheel was created containing:
demo_pkg-1.0.dist-info/namespace_packages.txt
whose content was the absolute path of:
sandbox\outside
The real acquired setuptools implementation was then invoked:
setuptools.wheel.Wheel(synthetic_wheel).install_as_egg(
sandbox_destination
)
The operation completed without exception.
Expected metadata was created under:
sandbox\destination\EGG-INFO
but setuptools also created:
sandbox\outside\__init__.py
The file did not exist before execution.
Its contents were:
__import__('pkg_resources').declare_namespace(__name__)
The unrelated canary remained byte-identical.
The sandbox was deleted after recording the evidence.
### Impact
A crafted wheel can cause setuptools to create a fixed-content
`__init__.py` outside the intended installation directory at a
filesystem path influenced by the wheel, with the permissions of the
process performing the installation.
The demonstrated reproduction required the target parent directory to
already exist.
Attack profile
How easy it is to exploit
- Attack vector
- Local
- Complexity
- High
- Privileges needed
- Low
- User interaction
- Active
What an attacker can affect
- Confidentiality
- None
- Integrity
- Low
- Availability
- Low
Affected packages
| Package | Affected | Fixed |
|---|---|---|
setuptoolsPyPI | 38.2.2 up to 84.1.0 | 84.1.0 |
setuptoolsTuxCare:PyPI | 75.8.0 up to 75.8.0.post3+tuxcare, plus listed versions 75.8.0.post1+tuxcare75.8.0.post2+tuxcare | 75.8.0.post3+tuxcare |
setuptoolsTuxCare:PyPI | 75.3.2 up to 75.3.2.post3+tuxcare, plus listed versions 75.3.2.post1+tuxcare75.3.2.post2+tuxcare | 75.3.2.post3+tuxcare |
setuptoolsTuxCare:PyPI | 75.0.0 up to 75.0.0.post3+tuxcare, plus listed versions 75.0.0.post1+tuxcare75.0.0.post2+tuxcare | 75.0.0.post3+tuxcare |
setuptoolsTuxCare:PyPI | 70.3.0 up to 70.3.0.post3+tuxcare, plus listed versions 70.3.0.post1+tuxcare70.3.0.post2+tuxcare | 70.3.0.post3+tuxcare |
setuptoolsPyPI | 38.2.2 up to 84.1.0, plus listed versions 38.2.338.2.438.2.538.3.038.4.0 Show 45 moreShow less38.4.138.5.038.5.138.5.238.6.038.6.138.7.039.0.039.0.139.1.039.2.040.0.040.1.040.1.140.2.040.3.040.4.040.4.140.4.240.4.340.5.040.6.040.6.140.6.240.6.340.7.040.7.140.7.240.7.340.8.040.9.041.0.041.0.141.1.041.2.041.3.041.4.041.5.041.5.141.6.042.0.042.0.142.0.243.0.044.0.0 | 84.1.0 |
What each source reports
GitHub Advisories Score used 1.0
setuptools: Windows path escape via namespace_packages.txt during legacy wheel installation Description
Summary
On Windows, attacker-controlled content in a wheel's
.dist-info/namespace_packages.txt can cause setuptools' legacy wheel
installation path to create an __init__.py outside the requested
destination_eggdir.
The attacker can influence the destination path, but not the contents of the created file.
Details
Tested setuptools commit:
6ead555c5fb29bc57fe6105b1bffc163f56fd558
Relevant source:
setuptools/wheel.py
SHA-256:
359b7e0d5dd8c3135653abe08a8ed5dc0352eadfd7df22cb5e2b5303b7c3cafd
The relevant call path is:
Wheel(wheel_path).install_as_egg(destination)
→ _install_as_egg
→ _convert_metadata
→ _fix_namespace_packages
A wheel can provide attacker-controlled content in:
<distribution>.dist-info/namespace_packages.txt
The namespace value is eventually used approximately as:
mod_dir = os.path.join(destination_eggdir, *mod.split('.'))
mod_init = os.path.join(mod_dir, '__init__.py')
If mod is a Windows drive-qualified absolute path containing no dots,
mod.split('.') preserves it as a single component.
On Windows, os.path.join(destination_eggdir, absolute_component) discards
the original base directory and resolves to the attacker-supplied absolute
path.
The existing wheel filename and ZIP traversal checks do not validate this
metadata value before it is subsequently interpreted as a filesystem path.
```markdown
### PoC
The reproduction was performed entirely inside a disposable temporary
directory.
Layout:
```text
sandbox/
destination/
outside/
input/
canary.txt
A synthetic wheel was created containing:
demo_pkg-1.0.dist-info/namespace_packages.txt
whose content was the absolute path of:
sandbox\outside
The real acquired setuptools implementation was then invoked:
setuptools.wheel.Wheel(synthetic_wheel).install_as_egg(
sandbox_destination
)
The operation completed without exception.
Expected metadata was created under:
sandbox\destination\EGG-INFO
but setuptools also created:
sandbox\outside\__init__.py
The file did not exist before execution.
Its contents were:
__import__('pkg_resources').declare_namespace(__name__)
The unrelated canary remained byte-identical.
The sandbox was deleted after recording the evidence.
### Impact
A crafted wheel can cause setuptools to create a fixed-content
`__init__.py` outside the intended installation directory at a
filesystem path influenced by the wheel, with the permissions of the
process performing the installation.
The demonstrated reproduction required the target parent directory to
already exist.
- Record
GHSA-xmj3-9gf7-h52w- CVSS 4.0
- 1.0 Low
CVSS:4.0/AV:L/AC:H/AT:P/PR:L/UI:A/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N - Weakness
- CWE-22
- Updated
- 2026-11-02
OSV 1.0
setuptools: Windows path escape via namespace_packages.txt during legacy wheel installation Description
Summary
On Windows, attacker-controlled content in a wheel's
.dist-info/namespace_packages.txt can cause setuptools' legacy wheel
installation path to create an __init__.py outside the requested
destination_eggdir.
The attacker can influence the destination path, but not the contents of the created file.
Details
Tested setuptools commit:
6ead555c5fb29bc57fe6105b1bffc163f56fd558
Relevant source:
setuptools/wheel.py
SHA-256:
359b7e0d5dd8c3135653abe08a8ed5dc0352eadfd7df22cb5e2b5303b7c3cafd
The relevant call path is:
Wheel(wheel_path).install_as_egg(destination)
→ _install_as_egg
→ _convert_metadata
→ _fix_namespace_packages
A wheel can provide attacker-controlled content in:
<distribution>.dist-info/namespace_packages.txt
The namespace value is eventually used approximately as:
mod_dir = os.path.join(destination_eggdir, *mod.split('.'))
mod_init = os.path.join(mod_dir, '__init__.py')
If mod is a Windows drive-qualified absolute path containing no dots,
mod.split('.') preserves it as a single component.
On Windows, os.path.join(destination_eggdir, absolute_component) discards
the original base directory and resolves to the attacker-supplied absolute
path.
The existing wheel filename and ZIP traversal checks do not validate this
metadata value before it is subsequently interpreted as a filesystem path.
```markdown
### PoC
The reproduction was performed entirely inside a disposable temporary
directory.
Layout:
```text
sandbox/
destination/
outside/
input/
canary.txt
A synthetic wheel was created containing:
demo_pkg-1.0.dist-info/namespace_packages.txt
whose content was the absolute path of:
sandbox\outside
The real acquired setuptools implementation was then invoked:
setuptools.wheel.Wheel(synthetic_wheel).install_as_egg(
sandbox_destination
)
The operation completed without exception.
Expected metadata was created under:
sandbox\destination\EGG-INFO
but setuptools also created:
sandbox\outside\__init__.py
The file did not exist before execution.
Its contents were:
__import__('pkg_resources').declare_namespace(__name__)
The unrelated canary remained byte-identical.
The sandbox was deleted after recording the evidence.
### Impact
A crafted wheel can cause setuptools to create a fixed-content
`__init__.py` outside the intended installation directory at a
filesystem path influenced by the wheel, with the permissions of the
process performing the installation.
The demonstrated reproduction required the target parent directory to
already exist.
- Record
GHSA-xmj3-9gf7-h52w- CVSS 4.0
- 1.0 Low
CVSS:4.0/AV:L/AC:H/AT:P/PR:L/UI:A/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N - Weakness
- CWE-22
- Updated
- 2026-11-02
OSVCLSA-2026-1793013431No score
TuxCare security update for setuptools (1 CVE)
TuxCare rebuilt setuptools to address 1 CVE. Fixed in 75.8.0.post3+tuxcare.
- Record
CLSA-2026-1793013431- Updated
- 2026-11-02
OSVCLSA-2026-1793018162No score
TuxCare security update for setuptools (1 CVE)
TuxCare rebuilt setuptools to address 1 CVE. Fixed in 75.3.2.post3+tuxcare.
- Record
CLSA-2026-1793018162- Updated
- 2026-11-02
OSVCLSA-2026-1793086192No score
TuxCare security update for setuptools (1 CVE)
TuxCare rebuilt setuptools to address 1 CVE. Fixed in 75.0.0.post3+tuxcare.
- Record
CLSA-2026-1793086192- Updated
- 2026-11-02
OSVCLSA-2026-1794464343No score
TuxCare security update for setuptools (1 CVE)
TuxCare rebuilt setuptools to address 1 CVE. Fixed in 70.3.0.post3+tuxcare.
- Record
CLSA-2026-1794464343- Updated
- 2026-11-12
History
| Recorded | Changed | Sources |
|---|---|---|
| affected, aliases, facts | OSV | |
| first indexed | GitHub Advisories, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
References
Left out here: 272 versions. The JSON record has them.