BREW-APM-GHSA-XMJ3-9GF7-H52W (GHSA-XMJ3-9GF7-H52W)

Vulnerability from osv_homebrew – Published: 2026-10-29 21:30 – Updated: 2026-10-29 21:30 – Source website
VLAI
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.


{
  "affected": [
    {
      "ecosystem_specific": {
        "fix": "bump",
        "range_state": "fixed",
        "resource": "setuptools",
        "resource_purl": "pkg:pypi/setuptools@84.2.1",
        "upstream_fixed_in": "84.1.0"
      },
      "package": {
        "ecosystem": "Homebrew",
        "name": "apm",
        "purl": "pkg:brew/apm"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0.26.0"
            },
            {
              "fixed": "0.35.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "database_specific": {
    "confidence": "high",
    "source": "matched",
    "strategy": "registry",
    "upstream_evidence": [
      {
        "ecosystem": "PyPI",
        "key": "pkg:pypi/setuptools@84.2.1",
        "name": "setuptools",
        "resource": "setuptools",
        "strategy": "registry",
        "subject_version": "84.2.1"
      }
    ]
  },
  "details": "### Summary\n\nOn Windows, attacker-controlled content in a wheel\u0027s\n`.dist-info/namespace_packages.txt` can cause setuptools\u0027 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\u2192 `_install_as_egg`\n\u2192 `_convert_metadata`\n\u2192 `_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(\u0027.\u0027))\nmod_init = os.path.join(mod_dir, \u0027__init__.py\u0027)\n\nIf mod is a Windows drive-qualified absolute path containing no dots,\nmod.split(\u0027.\u0027) 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__(\u0027pkg_resources\u0027).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.",
  "id": "BREW-apm-GHSA-xmj3-9gf7-h52w",
  "modified": "2026-10-29T21:30:32Z",
  "published": "2026-10-29T21:30:32Z",
  "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"
    }
  ],
  "schema_version": "1.7.3",
  "severity": [
    {
      "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",
      "type": "CVSS_V4"
    }
  ],
  "summary": "setuptools: Windows path escape via namespace_packages.txt during legacy wheel installation  Description",
  "upstream": [
    "GHSA-xmj3-9gf7-h52w"
  ]
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline

Sightings and recorded dates, grouped by day · UTC

JSON
Events included in the timeline

Loading timeline…


    Loading…

    Loading…

    Loading…

    Related by attack behaviour

    Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.