Every CVESearch/Get API key

GHSA-xmj3-9gf7-h52w

setuptools: Windows path escape via namespace_packages.txt during legacy wheel installation Description

1.0 LowFix available

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.

What does this mean?
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

Affected packages
PackageAffectedFixed
setuptools
PyPI
38.2.2 up to 84.1.0
84.1.0
setuptools
TuxCare: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
setuptools
TuxCare: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
setuptools
TuxCare: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
setuptools
TuxCare: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
setuptools
PyPI
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 less
38.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 used1.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 LowCVSS: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
OSV1.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 LowCVSS: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

History
RecordedChangedSources
affected, aliases, factsOSV
first indexedGitHub Advisories, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

References

This page doesn't list everything

Left out here: 272 versions. The JSON record has them.

MoveEnter Open