GHSA-xmj3-9gf7-h52w

Suggest an improvement
Source
https://github.com/advisories/GHSA-xmj3-9gf7-h52w
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-xmj3-9gf7-h52w/GHSA-xmj3-9gf7-h52w.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-xmj3-9gf7-h52w
Published
2026-10-23T21:44:11Z
Modified
2026-10-23T22:00:03Z
Severity
  • 1.0 (Low) CVSS_V4 - 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 CVSS Calculator
Summary
setuptools: Windows path escape via namespace_packages.txt during legacy wheel installation Description
Details

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.
Database specific
{
    "cwe_ids":  [
        "CWE-22"
    ],
    "github_reviewed":  true,
    "github_reviewed_at":  "2026-10-23T21:44:11Z",
    "nvd_published_at":  null,
    "severity":  "LOW"
}
References

Affected packages

PyPI / setuptools

Package

Name
setuptools
View open source insights on deps.dev
Purl
pkg:pypi/setuptools

Affected ranges

Type
ECOSYSTEM
Events
Introduced
38.2.2
Fixed
84.1.0

Affected versions

38.*
38.2.3
38.2.4
38.2.5
38.3.0
38.4.0
38.4.1
38.5.0
38.5.1
38.5.2
38.6.0
38.6.1
38.7.0
39.*
39.0.0
39.0.1
39.1.0
39.2.0
40.*
40.0.0
40.1.0
40.1.1
40.2.0
40.3.0
40.4.0
40.4.1
40.4.2
40.4.3
40.5.0
40.6.0
40.6.1
40.6.2
40.6.3
40.7.0
40.7.1
40.7.2
40.7.3
40.8.0
40.9.0
41.*
41.0.0
41.0.1
41.1.0
41.2.0
41.3.0
41.4.0
41.5.0
41.5.1
41.6.0
42.*
42.0.0
42.0.1
42.0.2
43.*
43.0.0
44.*
44.0.0
44.1.0
44.1.1
45.*
45.0.0
45.1.0
45.2.0
45.3.0
46.*
46.0.0
46.1.0
46.1.1
46.1.2
46.1.3
46.2.0
46.3.0
46.3.1
46.4.0
47.*
47.0.0
47.1.0
47.1.1
47.2.0
47.3.0
47.3.1
47.3.2
48.*
48.0.0
49.*
49.0.0
49.0.1
49.1.0
49.1.1
49.1.2
49.1.3
49.2.0
49.2.1
49.3.0
49.3.1
49.3.2
49.4.0
49.5.0
49.6.0
50.*
50.0.0
50.0.1
50.0.2
50.0.3
50.1.0
50.2.0
50.3.0
50.3.1
50.3.2
51.*
51.0.0
51.1.0
51.1.0.post20201221
51.1.1
51.1.2
51.2.0
51.3.0
51.3.1
51.3.2
51.3.3
52.*
52.0.0
53.*
53.0.0
53.1.0
54.*
54.0.0
54.1.0
54.1.1
54.1.2
54.1.3
54.2.0
56.*
56.0.0
56.1.0
56.2.0
57.*
57.0.0
57.1.0
57.2.0
57.3.0
57.4.0
57.5.0
58.*
58.0.0
58.0.1
58.0.2
58.0.3
58.0.4
58.1.0
58.2.0
58.3.0
58.4.0
58.5.0
58.5.1
58.5.2
58.5.3
59.*
59.0.1
59.1.0
59.1.1
59.2.0
59.3.0
59.4.0
59.5.0
59.6.0
59.7.0
59.8.0
60.*
60.0.0
60.0.1
60.0.2
60.0.3
60.0.4
60.0.5
60.1.0
60.1.1
60.2.0
60.3.0
60.3.1
60.4.0
60.5.0
60.6.0
60.7.0
60.7.1
60.8.0
60.8.1
60.8.2
60.9.0
60.9.1
60.9.2
60.9.3
60.10.0
61.*
61.0.0
61.1.0
61.1.1
61.2.0
61.3.0
61.3.1
62.*
62.0.0
62.1.0
62.2.0
62.3.0
62.3.1
62.3.2
62.3.3
62.3.4
62.4.0
62.5.0
62.6.0
63.*
63.0.0b1
63.0.0
63.1.0
63.2.0
63.3.0
63.4.0
63.4.1
63.4.2
63.4.3
64.*
64.0.0
64.0.1
64.0.2
64.0.3
65.*
65.0.0
65.0.1
65.0.2
65.1.0
65.1.1
65.2.0
65.3.0
65.4.0
65.4.1
65.5.0
65.5.1
65.6.0
65.6.1
65.6.2
65.6.3
65.7.0
66.*
66.0.0
66.1.0
66.1.1
67.*
67.0.0
67.1.0
67.2.0
67.3.1
67.3.2
67.3.3
67.4.0
67.5.0
67.5.1
67.6.0
67.6.1
67.7.0
67.7.1
67.7.2
67.8.0
68.*
68.0.0
68.1.0
68.1.2
68.2.0
68.2.1
68.2.2
69.*
69.0.0
69.0.1
69.0.2
69.0.3
69.1.0
69.1.1
69.2.0
69.3.0
69.3.1
69.4.0
69.4.1
69.4.2
69.5.0
69.5.1
70.*
70.0.0
70.1.0
70.1.1
70.2.0
70.3.0
71.*
71.0.0
71.0.1
71.0.2
71.0.3
71.0.4
71.1.0
72.*
72.0.0
72.1.0
72.2.0
73.*
73.0.0
73.0.1
74.*
74.0.0
74.1.0
74.1.1
74.1.2
74.1.3
75.*
75.0.0
75.1.0
75.2.0
75.3.0
75.3.1
75.3.2
75.3.3
75.3.4
75.4.0
75.5.0
75.6.0
75.7.0
75.8.0
75.8.1
75.8.2
75.9.0
75.9.1
76.*
76.0.0
76.1.0
77.*
77.0.1
77.0.3
78.*
78.0.1
78.0.2
78.1.0
78.1.1
79.*
79.0.0
79.0.1
80.*
80.0.0
80.0.1
80.1.0
80.2.0
80.3.0
80.3.1
80.4.0
80.6.0
80.7.0
80.7.1
80.8.0
80.9.0
80.10.1
80.10.2
81.*
81.0.0
82.*
82.0.0
82.0.1
83.*
83.0.0
84.*
84.0.0

Database specific

source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-xmj3-9gf7-h52w/GHSA-xmj3-9gf7-h52w.json"