sceptre · setuptools: Windows path escape via namespace_packages.txt during legacy wheel installation Description
Affects sceptreSource GCVE Database
CVSS 1.0 Low : upgrade to 4.7.0_3 when convenient.
Data as of 26 Oct 2026, 18:58 UTC
- Patch
- Available – fixed in 4.7.0_3
CVSS:4.0/AV:L/AC:H/AT:P/PR:L/UI:A/VC:N/VI:L/VA:L/SC:N/SI:N/SA:NWhat to do
Upgrade sceptre to 4.7.0_3.
Affected software
| Product | Affected | Fixed in |
|---|---|---|
Homebrewsceptrepkg:brew/sceptreOther threats | Affected
| Fixed in
|
Check your own hosts for these packages with threat-finder (opens in a new tab), our free open-source scanner.
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: ```python 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: ```python 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: ```python __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.
References
- github.com/pypa/setuptools/security/advisories/GHSA-xmj3-9gf7-h52w (opens in a new tab)
- github.com/pypa/setuptools/pull/5329 (opens in a new tab)
- github.com/pypa/setuptools/commit/6d07b8d17890dda58d944ac51e58f54af92d52af (opens in a new tab)
- github.com/pypa/setuptools (opens in a new tab)
- github.com/pypa/setuptools/releases/tag/v84.1.0 (opens in a new tab)