{"id":"GHSA-xmj3-9gf7-h52w","kind":"vulnerability","aliases":["GHSA-xmj3-9gf7-h52w"],"related":[],"title":"setuptools: Windows path escape via namespace_packages.txt during legacy wheel installation  Description","description":"### 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`<distribution>.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.","summary":"On Windows, a crafted wheel's namespace_packages.txt can make setuptools' legacy wheel-to-egg install write a fixed-content __init__.py to an attacker-chosen absolute path outside the install directory. The attacker controls only the location of the file, not its contents, and the write uses the installing process's permissions.","severity":"low","cvss":null,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"setuptools","fixed":["84.1.0"],"affected":">= 38.2.2, < 84.1.0","ecosystem":"pypi"}],"ecosystems":["pypi"],"tags":["cwe-22","package","pip","python","setuptools","windows"],"not_affected_if":["no-legacy-wheel-install","non-windows","setuptools-84.1.0-or-later"],"how_to_check":"pip show setuptools or python -m setuptools --version and compare against 84.1.0","fix":"Upgrade setuptools to 84.1.0 or later.\nOnly install wheels from trusted indexes until upgraded.","status":"open","published_at":"2026-10-23T21:44:11+00:00","first_seen_at":"2026-10-23T21:45:07+00:00","triaged_at":"2026-10-23T21:45:27+00:00","updated_at":"2026-10-23T21:45:27+00:00","category":"package","confidence":0.7,"triage":{"at":"2026-10-23T21:45:27Z","model":"claude-haiku-5-5","output":{"fix":"Upgrade setuptools to 84.1.0 or later.\nOnly install wheels from trusted indexes until upgraded.","reach":"local","impact":"low","summary":"On Windows, a crafted wheel's namespace_packages.txt can make setuptools' legacy wheel-to-egg install write a fixed-content __init__.py to an attacker-chosen absolute path outside the install directory. The attacker controls only the location of the file, not its contents, and the write uses the installing process's permissions.","category":"package","who_cares":["python","setuptools","windows","pip"],"confidence":0.7,"needs_auth":"no","worst_case":"Arbitrary fixed-content __init__.py written to attacker-chosen path","analyst_note":"","how_to_check":"pip show setuptools or python -m setuptools --version and compare against 84.1.0\nunknown\nunknown","needs_analyst":false,"not_affected_if":["non-windows","no-legacy-wheel-install","setuptools-84.1.0-or-later"],"user_interaction":"no","needs_special_setup":"yes","severity_if_unknown":"low"},"cost_usd":0.000569,"input_tokens":3747,"output_tokens":388,"severity_before":"low"},"analyst_status":null,"analyst_note":null,"search":"'-22':24B '-256':141C '/namespace_packages.txt':99C,175C '359b7e0d5dd8c3135653abe08a8ed5dc0352eadfd7df22cb5e2b5303b7c3cafd':142C '6ead555c5fb29bc57fe6105b1bffc163f56fd558':136C '9gf7':15A,20A 'absolut':58B,206C,223C,237C,293C 'acquir':300C 'alreadi':416C 'also':332C 'approxim':182C 'attack':56B,65B,89C,118C,168C,235C 'attacker-chosen':55B 'attacker-control':88C,167C 'attacker-suppli':234C 'base':228C 'byte':360C 'byte-ident':359C 'call':145C 'canari':357C 'canary.txt':280C 'caus':101C,375C 'check':246C 'chosen':57B 'commit':135C 'complet':317C 'compon':217C,224C 'contain':208C,286C 'content':50B,75B,91C,127C,170C,290C,346C,382C 'control':66B,90C,169C 'convert':158C 'craft':33B,372C 'creat':108C,130C,285C,323C,333C,378C 'cwe':23B 'declar':352C 'delet':365C 'demo_pkg-1.0.dist':287C 'demonstr':408C 'descript':11A 'destin':115C,122C,154C,188C,221C,277C,314C,326C 'detail':132C 'dir':186C,195C 'directori':63B,229C,273C,389C,414C 'discard':225C 'dispos':271C 'dist':97C,173C 'dist-info':96C,172C 'dot':210C 'drive':204C 'drive-qualifi':203C 'egg':44B,153C,157C,312C,328C 'egg-info':327C 'eggdir':116C,189C,222C 'entir':268C 'escap':4A 'eventu':180C 'evid':369C 'except':319C 'execut':344C 'exist':240C,342C,417C 'expect':320C 'file':72B,131C,339C 'filenam':242C 'filesystem':260C,392C 'fix':49B,160C,381C 'fixed-cont':48B,380C 'ghsa':13A,18A 'ghsa-xmj3-9gf7-h52w':12A,17A 'h52w':16A,21A 'ident':361C 'impact':370C 'implement':302C 'import':349C 'influenc':120C,394C 'info':98C,174C,329C 'info/namespace_packages.txt':288C 'init':51B,110C,192C,196C,336C,383C 'input':279C 'insid':269C 'instal':10A,45B,62B,81B,105C,151C,155C,310C,388C,406C 'intend':387C 'interpret':257C 'invok':305C 'layout':274C 'legaci':8A,40B,103C 'locat':69B 'make':38B 'markdown':262C 'metadata':159C,251C,321C 'mod':185C,191C,194C,199C 'mod.split':190C,211C 'name':354C 'namespac':161C,177C,353C 'namespace_packages.txt':6A,36B 'oper':316C 'origin':227C 'os.path.join':187C,193C,220C 'outsid':60B,112C,278C,297C,335C,385C 'packag':25B,162C 'parent':413C 'path':3A,59B,106C,123C,146C,150C,207C,238C,261C,294C,393C 'perform':267C,404C 'permiss':84B,400C 'pip':26B 'pkg':350C 'poc':263C 'preserv':212C 'process':82B,403C 'provid':166C 'py':52B,111C,197C,337C,384C 'python':27B,184C,306C,348C 'qualifi':205C 'real':299C 'record':367C 'relev':137C,144C 'remain':358C 'reproduct':265C,409C 'request':114C 'requir':410C 'resolv':231C 'resourc':351C 'sandbox':276C,296C,313C,325C,334C,363C 'setuptool':1A,28B,39B,102C,134C,301C,331C,376C 'setuptools':22A 'setuptools.wheel.wheel':307C 'setuptools/wheel.py':139C 'sha':140C 'singl':216C 'sourc':138C 'subsequ':256C 'summari':85C 'suppli':236C 'synthet':282C,308C 'target':412C 'temporari':272C 'test':133C 'text':275C 'travers':245C 'unrel':356C 'use':79B,181C 'valid':249C 'valu':178C,252C 'via':5A 'wheel':9A,34B,42B,94C,104C,148C,149C,164C,241C,283C,309C,373C,397C 'wheel-to-egg':41B 'whose':289C 'window':2A,29B,31B,87C,202C,219C 'without':318C 'write':46B,78B 'xmj3':14A,19A 'zip':244C","nicknames":[],"mitigations":[],"who_affected":null,"tier":"C","verified":{},"analyst_revisit_at":null,"first_public_at":"2026-10-23T21:44:11+00:00","kev":null,"cvss_vector":null,"cvss_by":null,"poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-xmj3-9gf7-h52w","url":"https://github.com/advisories/GHSA-xmj3-9gf7-h52w","severity":"low","published_at":"2026-10-23T21:44:11+00:00","modified_at":"2026-10-23T21:44:13+00:00","first_seen_at":"2026-10-23T21:45:07+00:00","fetched_at":"2026-10-23T21:51:54+00:00","note":null}],"documents":[],"notice":"Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai"}