{"id":"GHSA-xmj3-9gf7-h52w","summary":"setuptools: Windows path escape via namespace_packages.txt during legacy wheel installation  Description","details":"### Summary\n\nOn Windows, attacker-controlled content in a wheel's\n`.dist-info/namespace_packages.txt` can cause setuptools' legacy wheel\ninstallation path to create an `__init__.py` outside the requested\n`destination_eggdir`.\n\nThe attacker can influence the destination path, but not the contents of\nthe created file.\n\n### Details\n\nTested setuptools commit:\n\n`6ead555c5fb29bc57fe6105b1bffc163f56fd558`\n\nRelevant source:\n\n`setuptools/wheel.py`\n\nSHA-256:\n\n`359b7e0d5dd8c3135653abe08a8ed5dc0352eadfd7df22cb5e2b5303b7c3cafd`\n\nThe relevant call path is:\n\n`Wheel(wheel_path).install_as_egg(destination)`\n→ `_install_as_egg`\n→ `_convert_metadata`\n→ `_fix_namespace_packages`\n\nA wheel can provide attacker-controlled content in:\n\n`\u003cdistribution\u003e.dist-info/namespace_packages.txt`\n\nThe namespace value is eventually used approximately as:\n\n```python\nmod_dir = os.path.join(destination_eggdir, *mod.split('.'))\nmod_init = os.path.join(mod_dir, '__init__.py')\n\nIf mod is a Windows drive-qualified absolute path containing no dots,\nmod.split('.') preserves it as a single component.\nOn Windows, os.path.join(destination_eggdir, absolute_component) discards\nthe original base directory and resolves to the attacker-supplied absolute\npath.\nThe existing wheel filename and ZIP traversal checks do not validate this\nmetadata value before it is subsequently interpreted as a filesystem path.\n\n```markdown\n### PoC\n\nThe reproduction was performed entirely inside a disposable temporary\ndirectory.\n\nLayout:\n\n```text\nsandbox/\n    destination/\n    outside/\n    input/\n    canary.txt\n```\n\nA synthetic wheel was created containing:\n\n`demo_pkg-1.0.dist-info/namespace_packages.txt`\n\nwhose content was the absolute path of:\n\n`sandbox\\outside`\n\nThe real acquired setuptools implementation was then invoked:\n\n```python\nsetuptools.wheel.Wheel(synthetic_wheel).install_as_egg(\n    sandbox_destination\n)\n```\n\nThe operation completed without exception.\n\nExpected metadata was created under:\n\n`sandbox\\destination\\EGG-INFO`\n\nbut setuptools also created:\n\n`sandbox\\outside\\__init__.py`\n\nThe file did not exist before execution.\n\nIts contents were:\n\n```python\n__import__('pkg_resources').declare_namespace(__name__)\n```\n\nThe unrelated canary remained byte-identical.\n\nThe sandbox was deleted after recording the evidence.\n```\n\n\n### Impact\n\nA crafted wheel can cause setuptools to create a fixed-content\n`__init__.py` outside the intended installation directory at a\nfilesystem path influenced by the wheel, with the permissions of the\nprocess performing the installation.\n\nThe demonstrated reproduction required the target parent directory to\nalready exist.","modified":"2026-10-23T22:00:03.739009766Z","published":"2026-10-23T21:44:11Z","database_specific":{"nvd_published_at":null,"cwe_ids":["CWE-22"],"severity":"LOW","github_reviewed":true,"github_reviewed_at":"2026-10-23T21:44:11Z"},"references":[{"type":"WEB","url":"https://github.com/pypa/setuptools/security/advisories/GHSA-xmj3-9gf7-h52w"},{"type":"WEB","url":"https://github.com/pypa/setuptools/pull/5329"},{"type":"WEB","url":"https://github.com/pypa/setuptools/commit/6d07b8d17890dda58d944ac51e58f54af92d52af"},{"type":"PACKAGE","url":"https://github.com/pypa/setuptools"},{"type":"WEB","url":"https://github.com/pypa/setuptools/releases/tag/v84.1.0"}],"affected":[{"package":{"name":"setuptools","ecosystem":"PyPI","purl":"pkg:pypi/setuptools"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"38.2.2"},{"fixed":"84.1.0"}]}],"versions":["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.0.0","39.0.1","39.1.0","39.2.0","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.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.0.0","42.0.1","42.0.2","43.0.0","44.0.0","44.1.0","44.1.1","45.0.0","45.1.0","45.2.0","45.3.0","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.0.0","47.1.0","47.1.1","47.2.0","47.3.0","47.3.1","47.3.2","48.0.0","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.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.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.0.0","53.0.0","53.1.0","54.0.0","54.1.0","54.1.1","54.1.2","54.1.3","54.2.0","56.0.0","56.1.0","56.2.0","57.0.0","57.1.0","57.2.0","57.3.0","57.4.0","57.5.0","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.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.0.0","60.0.1","60.0.2","60.0.3","60.0.4","60.0.5","60.1.0","60.1.1","60.10.0","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","61.0.0","61.1.0","61.1.1","61.2.0","61.3.0","61.3.1","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.0.0","63.0.0b1","63.1.0","63.2.0","63.3.0","63.4.0","63.4.1","63.4.2","63.4.3","64.0.0","64.0.1","64.0.2","64.0.3","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.0.0","66.1.0","66.1.1","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.0.0","68.1.0","68.1.2","68.2.0","68.2.1","68.2.2","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.0.0","70.1.0","70.1.1","70.2.0","70.3.0","71.0.0","71.0.1","71.0.2","71.0.3","71.0.4","71.1.0","72.0.0","72.1.0","72.2.0","73.0.0","73.0.1","74.0.0","74.1.0","74.1.1","74.1.2","74.1.3","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.0.0","76.1.0","77.0.1","77.0.3","78.0.1","78.0.2","78.1.0","78.1.1","79.0.0","79.0.1","80.0.0","80.0.1","80.1.0","80.10.1","80.10.2","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","81.0.0","82.0.0","82.0.1","83.0.0","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"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"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"}]}