PyPI / setuptools / 84.2.1

setuptools 84.2.1

Source
https://github.com/pypa/setuptools
Tag scanned
v84.2.1
Compared with
v84.2.0 (0 signals introduced since)
Files
417 scanned, 125 skipped (binary, oversized, or unrecognised)
Signals
0 critical · 0 warn · 59 info
Scan time
15.9 s
LLM review
Reviewed by claude-haiku-5-5 (Anthropic): 22 of 22 warn and critical signals; 22 demoted to info.
Disclude version
4.1.0
Scanned (UTC)
2026-10-21 20:28

Signals

Ordered by severity, then signals introduced in this release. Hidden characters are shown as ⟨U+…⟩.

  1. info critical → info by review dynamic-execution launcher.c:328:9

    `execv` called with a non-literal path (ast pass, confidence 0.90)

            /* Use exec, we don't need to wait for the GUI to finish */
            execv(ptr, (const char * const *)(newargs));
            return fail("Could not exec %s", ptr);   /* shouldn't get here! */
        }
    likely benign LLM, confidence 0.80 Launcher execv's a computed target path to start a GUI executable.
    Reasoning

    execv in a launcher after building newargs for a GUI program is a standard exec pattern; the path is runtime-determined but this is the program's own launch logic, with an error fallback. Not obfuscation by itself, though the non-literal path warrants a glance at its origin.

  2. info critical → info by review dynamic-execution setuptools/_distutils/command/bdist_rpm.py:369:15

    `os.popen` called on a non-literal command — runtime-determined shell invocation (ast pass, confidence 0.85)

    
            out = os.popen(q_cmd)
            try:
                binary_rpms = []
    likely benign LLM, confidence 0.85 bdist_rpm runs an rpm query command via os.popen to list built binary RPMs.
    Reasoning

    os.popen on a constructed rpm query command is ordinary build tooling in distutils' bdist_rpm, used to query built RPM files. Known legitimate setuptools code.

  3. info warn → info by review dynamic-execution setuptools/_distutils/command/bdist_rpm.py:436:23

    `subprocess.getoutput` called with a literal shell command (ast pass, confidence 0.70)

            # RHEL6 (and probably derivatives)
            vendor_hook = subprocess.getoutput('rpm --eval %{__os_install_post}')
            # Generate a potential replacement value for __os_install_post (whilst
            # normalizing the whitespace to simplify the test for whether the
    likely benign LLM, confidence 0.90 Runs a hardcoded rpm --eval command to read the __os_install_post macro.
    Reasoning

    A fixed literal rpm --eval command for detecting the RHEL install hook is benign build logic with no external input.

  4. info warn → info by review dynamic-execution setuptools/_distutils/compilers/C/base.py:1130:28

    `eval` called on a non-literal expression (ast pass, confidence 0.70)

            expected = '"static", "shared", "dylib", "xcode_stub"'
            if lib_type not in eval(expected):
                raise ValueError(f"'lib_type' must be {expected}")
            fmt = getattr(self, lib_type + "_lib_format")
    likely benign LLM, confidence 0.85 eval of a constant tuple string to validate lib_type.
    Reasoning

    eval on a fixed string literal defined a line above, listing valid library types. Unusual style but no external input reaches it.

  5. info warn → info by review dynamic-execution setuptools/_distutils/core.py:250:17

    `exec` called on a non-literal expression (ast pass, confidence 0.70)

                    code = f.read().replace(r'\r\n', r'\n')
                    exec(code, g)  # noqa: S102 # executing the setup script is the point
            finally:
                sys.argv = save_argv
    likely benign LLM, confidence 0.90 run_setup executes the user's setup.py script by design.
    Reasoning

    exec of the setup script is the documented purpose of distutils run_setup; the code comes from the project being built, which is expected.

  6. info critical → info by review dynamic-import setuptools/_distutils/dist.py:873:17

    `__import__` called on a non-literal expression (ast pass, confidence 0.70)

                try:
                    __import__(module_name)
                    module = sys.modules[module_name]
                except ImportError:
    likely benign LLM, confidence 0.90 Imports a command module by name for distutils command resolution.
    Reasoning

    Dynamic __import__ of a command module name inside a try/except ImportError is standard plugin loading in distutils.

  7. identifier `Dεbug` mixes scripts: Latin + Greek (in string/comment) (raw pass, confidence 0.40)

            caplog.set_level(logging.DEBUG)
            log.debug('Dεbug\tMėssãge')
            log.fatal('Fαtal\tÈrrōr')
            assert caplog.messages == ['Dεbug\tMėssãge', 'Fαtal\tÈrrōr']
  8. identifier `Fαtal` contains homoglyph candidates: α (03B1)→a (in string/comment) (raw pass, confidence 0.35)

            log.debug('Dεbug\tMėssãge')
            log.fatal('Fαtal\tÈrrōr')
            assert caplog.messages == ['Dεbug\tMėssãge', 'Fαtal\tÈrrōr']
  9. identifier `Fαtal` mixes scripts: Latin + Greek (in string/comment) (raw pass, confidence 0.40)

            log.debug('Dεbug\tMėssãge')
            log.fatal('Fαtal\tÈrrōr')
            assert caplog.messages == ['Dεbug\tMėssãge', 'Fαtal\tÈrrōr']
  10. identifier `Dεbug` mixes scripts: Latin + Greek (in string/comment) (raw pass, confidence 0.40)

            log.fatal('Fαtal\tÈrrōr')
            assert caplog.messages == ['Dεbug\tMėssãge', 'Fαtal\tÈrrōr']
  11. identifier `Fαtal` contains homoglyph candidates: α (03B1)→a (in string/comment) (raw pass, confidence 0.35)

            log.fatal('Fαtal\tÈrrōr')
            assert caplog.messages == ['Dεbug\tMėssãge', 'Fαtal\tÈrrōr']
  12. identifier `Fαtal` mixes scripts: Latin + Greek (in string/comment) (raw pass, confidence 0.40)

            log.fatal('Fαtal\tÈrrōr')
            assert caplog.messages == ['Dεbug\tMėssãge', 'Fαtal\tÈrrōr']
  13. info warn → info by review dynamic-execution setuptools/_distutils/util.py:397:21

    `compile` called on a non-literal expression (ast pass, confidence 0.70)

                        log.info("byte-compiling %s to %s", file, cfile_base)
                        compile(file, cfile, dfile)
                    else:
                        log.debug("skipping byte-compilation of %s to %s", file, cfile_base)
    likely benign LLM, confidence 0.90 Byte-compiles Python files during install, a standard distutils routine.
    Reasoning

    compile of a source file during byte-compilation is the normal distutils byte-compile utility operation.

  14. info warn → info by review decoder-decompress-payload setuptools/_vendor/backports/tarfile/__init__.py:479:20

    file imports decoder module(s) zlib, bz2, lzma, gzip and calls `.decompress()` — embedded payload decompressed at runtime (ast pass, confidence 0.75)

            """
            self.cmp = self.zlib.decompressobj(-self.zlib.MAX_WBITS)
            self.dbuf = b""
    likely benign LLM, confidence 0.85 Streaming decompression for reading compressed tar archives.
    Reasoning

    zlib decompressobj in a streaming tar reader is the normal implementation of decompressing tar data for reading archives.

  15. info warn → info by review dynamic-execution setuptools/_vendor/jaraco/functools/__init__.py:563:28

    `eval` called on a non-literal expression (ast pass, confidence 0.70)

                    try:
                        return eval(use)
                    except TypeError:
                        return replace
    likely benign LLM, confidence 0.85 Vendored jaraco helper evaluates an expression string.
    Reasoning

    eval of a 'use' expression in a jaraco helper is a known pattern for evaluating a type-ish or expression argument; it is a vendored utility and not reached from untrusted input in this snippet.

  16. info warn → info by review dynamic-execution setuptools/_vendor/packaging/licenses/__init__.py:144:9

    `compile` called on a non-literal expression (ast pass, confidence 0.70)

        try:
            compile(python_expression, "", "eval")
        except SyntaxError:
            message = f"Invalid license expression: {raw_license_expression!r}"
    likely benign LLM, confidence 0.90 Syntax-checks a license expression string via compile.
    Reasoning

    compile with mode eval is used only to syntax-check a license expression for validation; it does not execute the code.

  17. info warn → info by review dynamic-execution setuptools/build_meta.py:317:13

    `exec` called on a non-literal expression (ast pass, confidence 0.70)

            try:
                exec(code, locals())  # noqa: S102 # exec is intentional here
            except SystemExit as e:
                if e.code:
    likely benign LLM, confidence 0.85 PEP 517 backend executes the project's setup script.
    Reasoning

    build_meta executes the project's setup.py in a locals namespace as part of the PEP 517 build backend; that is its purpose.

  18. info warn → info by review dynamic-execution setuptools/command/bdist_egg.py:396:12

    `marshal.load` deserializes Python bytecode from bytes — code-object sink (ast pass, confidence 0.75)

        f.read(skip)
        code = marshal.load(f)
        f.close()
        safe = True
    likely benign LLM, confidence 0.80 Reads compiled bytecode from a module file to check zip safety.
    Reasoning

    marshal.load of bytecode from a .pyc during egg scanning for zip-safety analysis is ordinary setuptools behavior, though marshal is a code-object sink in general.

  19. info warn → info by review frame-introspection setuptools/command/install.py:114:18

    `inspect.getouterframes` — call-stack frame introspection (ast pass, confidence 0.70)

    
            frames = inspect.getouterframes(run_frame)
            for frame in frames[2:4]:
                (caller,) = frame[:1]
    likely benign LLM, confidence 0.90 Inspects the call stack to find the caller of the install run.
    Reasoning

    getouterframes is used to locate the caller of a setup run for install behavior; standard introspection in setuptools.

  20. line length 1035 characters (raw pass, confidence 0.50)

                    raise JsonSchemaValueException("" + (name_prefix or "data") + ".build-system must be object", value=data__buildsystem, name="" + (name_prefix or "data") + ".build-system", definition={'type': 'object', 'description': 'Table used to store build-related data', 'additionalProperties': False, 'properties': 
  21. line length 792 characters (raw pass, confidence 0.50)

                    raise JsonSchemaValueException("" + (name_prefix or "data") + ".dependency-groups must be object", value=data__dependencygroups, name="" + (name_prefix or "data") + ".dependency-groups", definition={'type': 'object', 'description': 'Dependency groups following PEP 735', 'additionalProperties': False, 'p
  22. line length 891 characters (raw pass, confidence 0.50)

                    raise JsonSchemaValueException("" + (name_prefix or "data") + ".package-dir must be object", value=data__packagedir, name="" + (name_prefix or "data") + ".package-dir", definition={'$$description': [':class:`dict`-like structure mapping from package names to directories where their', 'code can be found.
  23. line length 968 characters (raw pass, confidence 0.50)

                    raise JsonSchemaValueException("" + (name_prefix or "data") + ".package-data must be object", value=data__packagedata, name="" + (name_prefix or "data") + ".package-data", definition={'$$description': ['Mapping from package names to lists of glob patterns.', 'Usually this option is not needed when using
  24. line length 934 characters (raw pass, confidence 0.50)

                    raise JsonSchemaValueException("" + (name_prefix or "data") + ".exclude-package-data must be object", value=data__excludepackagedata, name="" + (name_prefix or "data") + ".exclude-package-data", definition={'$$description': ['Mapping from package names to lists of glob patterns that should be excluded',
  25. line length 1720 characters (raw pass, confidence 0.50)

                    raise JsonSchemaValueException("" + (name_prefix or "data") + ".ext-modules must be array", value=data__extmodules, name="" + (name_prefix or "data") + ".ext-modules", definition={'description': 'Extension modules to be compiled by setuptools', 'type': 'array', 'items': {'$id': '#/definitions/ext-module
  26. line length 791 characters (raw pass, confidence 0.50)

                    raise JsonSchemaValueException("" + (name_prefix or "data") + ".data-files must be object", value=data__datafiles, name="" + (name_prefix or "data") + ".data-files", definition={'$$description': ['``dict``-like structure where each key represents a directory and', 'the value is a list of glob patterns t
  27. line length 1584 characters (raw pass, confidence 0.50)

            raise JsonSchemaValueException("" + (name_prefix or "data") + " must be object", value=data, name="" + (name_prefix or "data") + "", definition={'$id': '#/definitions/ext-module', 'title': 'Extension module', 'description': 'Parameters to construct a :class:`setuptools.Extension` object', 'type': 'object', 'req
  28. line length 1643 characters (raw pass, confidence 0.50)

                raise JsonSchemaValueException("" + (name_prefix or "data") + " must contain " + (str(sorted(data__missing_keys)) + " properties"), value=data, name="" + (name_prefix or "data") + "", definition={'$id': '#/definitions/ext-module', 'title': 'Extension module', 'description': 'Parameters to construct a :class
  29. line length 1636 characters (raw pass, confidence 0.50)

                raise JsonSchemaValueException("" + (name_prefix or "data") + " must not contain "+str(data_keys)+" properties", value=data, name="" + (name_prefix or "data") + "", definition={'$id': '#/definitions/ext-module', 'title': 'Extension module', 'description': 'Parameters to construct a :class:`setuptools.Extens
  30. line length 1230 characters (raw pass, confidence 0.50)

            raise JsonSchemaValueException("" + (name_prefix or "data") + " must be object", value=data, name="" + (name_prefix or "data") + "", definition={'$id': '#/definitions/find-directive', 'title': "'find:' directive", 'type': 'object', 'additionalProperties': False, 'properties': {'find': {'type': 'object', '$$desc
  31. line length 1110 characters (raw pass, confidence 0.50)

                    raise JsonSchemaValueException("" + (name_prefix or "data") + ".find must be object", value=data__find, name="" + (name_prefix or "data") + ".find", definition={'type': 'object', '$$description': ['Dynamic `package discovery', '<https://setuptools.pypa.io/en/latest/userguide/package_discovery.html>`_.']
  32. line length 1282 characters (raw pass, confidence 0.50)

                raise JsonSchemaValueException("" + (name_prefix or "data") + " must not contain "+str(data_keys)+" properties", value=data, name="" + (name_prefix or "data") + "", definition={'$id': '#/definitions/find-directive', 'title': "'find:' directive", 'type': 'object', 'additionalProperties': False, 'properties':
  33. line length 1502 characters (raw pass, confidence 0.50)

                    raise JsonSchemaValueException("" + (name_prefix or "data") + ".readme must be valid exactly by one definition" + (" (" + str(data__readme_one_of_count12) + " matches found)"), value=data__readme, name="" + (name_prefix or "data") + ".readme", definition={'$$description': ['`Full/detailed description of
  34. line length 935 characters (raw pass, confidence 0.50)

                    raise JsonSchemaValueException("" + (name_prefix or "data") + ".license must be valid exactly by one definition" + (" (" + str(data__license_one_of_count14) + " matches found)"), value=data__license, name="" + (name_prefix or "data") + ".license", definition={'description': '`Project license <https://pe
  35. line length 963 characters (raw pass, confidence 0.50)

                    raise JsonSchemaValueException("" + (name_prefix or "data") + ".authors must be array", value=data__authors, name="" + (name_prefix or "data") + ".authors", definition={'type': 'array', 'items': {'$id': '#/definitions/author', 'title': 'Author or Maintainer', '$comment': 'https://peps.python.org/pep-062
  36. line length 920 characters (raw pass, confidence 0.50)

                    raise JsonSchemaValueException("" + (name_prefix or "data") + ".maintainers must be array", value=data__maintainers, name="" + (name_prefix or "data") + ".maintainers", definition={'type': 'array', 'items': {'$id': '#/definitions/author', 'title': 'Author or Maintainer', '$comment': 'https://peps.python
  37. line length 837 characters (raw pass, confidence 0.50)

                            raise JsonSchemaValueException("" + (name_prefix or "data") + ".dynamic[{data__dynamic_x}]".format(**locals()) + " must be one of ['version', 'description', 'readme', 'requires-python', 'license', 'license-files', 'authors', 'maintainers', 'keywords', 'classifiers', 'urls', 'scripts', 'gui-scrip
  38. line length 968 characters (raw pass, confidence 0.50)

            raise JsonSchemaValueException("" + (name_prefix or "data") + " must be object", value=data, name="" + (name_prefix or "data") + "", definition={'$id': '#/definitions/entry-point-group', 'title': 'Entry-points', 'type': 'object', '$$description': ['Entry-points are grouped together to indicate what sort of capa
  39. line length 1020 characters (raw pass, confidence 0.50)

                raise JsonSchemaValueException("" + (name_prefix or "data") + " must not contain "+str(data_keys)+" properties", value=data, name="" + (name_prefix or "data") + "", definition={'$id': '#/definitions/entry-point-group', 'title': 'Entry-points', 'type': 'object', '$$description': ['Entry-points are grouped to
  40. line length 1011 characters (raw pass, confidence 0.50)

                    raise JsonSchemaValueException("" + (name_prefix or "data") + " must be named by propertyName definition", value=data, name="" + (name_prefix or "data") + "", definition={'$id': '#/definitions/entry-point-group', 'title': 'Entry-points', 'type': 'object', '$$description': ['Entry-points are grouped toge
  41. info warn → info by review dynamic-execution setuptools/depends.py:132:24

    `marshal.load` deserializes Python bytecode from bytes — code-object sink (ast pass, confidence 0.75)

                    f.read(8)  # skip magic & date
                    code = marshal.load(f)
                elif kind == PY_FROZEN:
                    code = _imp.get_frozen_object(module, paths)
    likely benign LLM, confidence 0.90 Reads a module's compiled code object to extract constants.
    Reasoning

    marshal.load of a module's bytecode is the standard way depends.py extracts constants from compiled modules for version detection.

  42. info warn → info by review decoder-import-with-exec setuptools/depends.py:136:24

    file imports decoder module(s) marshal and calls `exec`/`eval`/`compile` — multi-stage payload shape (ast pass, confidence 0.80)

                elif kind == PY_SOURCE:
                    code = compile(f.read(), path, 'exec')
                else:
                    # Not something we can parse; we'll have to import it.  :(
    likely benign LLM, confidence 0.90 Setuptools dependency helper compiles Python source it reads from disk.
    Reasoning

    compile of source read from a file in a module-introspection helper for setuptools; the 'marshal' import is part of standard bytecode handling. Legitimate code for inspecting dependency modules, not a dropper.

  43. info warn → info by review dynamic-execution setuptools/depends.py:136:24

    `compile` called on a non-literal expression (ast pass, confidence 0.70)

                elif kind == PY_SOURCE:
                    code = compile(f.read(), path, 'exec')
                else:
                    # Not something we can parse; we'll have to import it.  :(
    likely benign LLM, confidence 0.90 compile call on file contents inside setuptools' module inspection code.
    Reasoning

    compile(f.read(), path, 'exec') on a source file in setuptools' depends module is a standard way to analyze a module's code object; the non-literal argument is expected here.

  44. info critical → info by review dynamic-import setuptools/extension.py:24:9

    `__import__` called on a non-literal expression (ast pass, confidence 0.70)

            # from (cython_impl) import build_ext
            __import__(cython_impl, fromlist=['build_ext']).build_ext  # noqa: B018 # evaluated to trigger validation/side effect
        except Exception:  # noqa: BLE001 # intentional broad fallback
            return False
    likely benign LLM, confidence 0.90 Setuptools extension code dynamically imports the Cython build_ext module to validate availability.
    Reasoning

    This is a deliberate import used to trigger Cython validation within a try/except returning False; the broad fallback is documented and the import target is an internal module name. Dynamic import is common and not payload-like here.

  45. info warn → info by review dynamic-execution setuptools/launch.py:31:12

    `compile` called on a non-literal expression (ast pass, confidence 0.70)

        norm_script = script.replace('\\r\\n', '\\n')
        code = compile(norm_script, script_name, 'exec')
        exec(code, namespace)  # noqa: S102 # exec is intentional here
    likely benign LLM, confidence 0.92 Setuptools launch helper compiles and executes a user setup script.
    Reasoning

    setuptools' launcher compiles and executes a setup script in a namespace, which is its documented purpose (running setup.py under setuptools control). The exec is explicit and commented as intentional.

  46. info warn → info by review dynamic-execution setuptools/launch.py:32:5

    `exec` called on a non-literal expression (ast pass, confidence 0.70)

        code = compile(norm_script, script_name, 'exec')
        exec(code, namespace)  # noqa: S102 # exec is intentional here
    likely benign LLM, confidence 0.92 Setuptools launch helper executes the compiled setup script.
    Reasoning

    exec of the compiled setup script is the core intended behavior of setuptools' launch module; the code is transparent and not encoded or hidden.

  47. identifier `Γαμα` contains homoglyph candidates: α (03B1)→a, α (03B1)→a (in string/comment) (raw pass, confidence 0.35)

      {name = "John X. Ãørçeč", email = "john@utf8.org"},
      {name = "Γαμα קּ 東", email = "gama@utf8.org"},
    ]
    classifiers = [
  48. identifier `Γαμα` mixes scripts: Greek + Latin (in string/comment) (raw pass, confidence 0.40)

      {name = "John X. Ãørçeč", email = "john@utf8.org"},
      {name = "Γαμα קּ 東", email = "gama@utf8.org"},
    ]
    classifiers = [
  49. identifier `Γαμα` contains homoglyph candidates: α (03B1)→a, α (03B1)→a (in string/comment) (raw pass, confidence 0.35)

                    'Brett Cannon <brett@python.org>, "John X. Ãørçeč" <john@utf8.org>, '
                    'Γαμα קּ 東 <gama@utf8.org>'
                ),
                id='non-international-emails',
  50. identifier `Γαμα` mixes scripts: Greek + Latin (in string/comment) (raw pass, confidence 0.40)

                    'Brett Cannon <brett@python.org>, "John X. Ãørçeč" <john@utf8.org>, '
                    'Γαμα קּ 東 <gama@utf8.org>'
                ),
                id='non-international-emails',
  51. identifier `Γαμα` contains homoglyph candidates: α (03B1)→a, α (03B1)→a (in string/comment) (raw pass, confidence 0.35)

                version = 42
                author_email = "John X. Ãørçeč" <john@utf8.org>, Γαμα קּ 東 <gama@utf8.org>
                long_description = file: README.rst
                """
  52. identifier `Γαμα` mixes scripts: Greek + Latin (in string/comment) (raw pass, confidence 0.40)

                version = 42
                author_email = "John X. Ãørçeč" <john@utf8.org>, Γαμα קּ 東 <gama@utf8.org>
                long_description = file: README.rst
                """
  53. identifier `Γαμα` contains homoglyph candidates: α (03B1)→a, α (03B1)→a (in string/comment) (raw pass, confidence 0.35)

    Version: 42
    Author-email: "John X. Ãørçeč" <john@utf8.org>, Γαμα קּ 東 <gama@utf8.org>
  54. identifier `Γαμα` mixes scripts: Greek + Latin (in string/comment) (raw pass, confidence 0.40)

    Version: 42
    Author-email: "John X. Ãørçeč" <john@utf8.org>, Γαμα קּ 東 <gama@utf8.org>
  55. identifier `Γαμα` contains homoglyph candidates: α (03B1)→a, α (03B1)→a (in string/comment) (raw pass, confidence 0.35)

        assert 'Author-email: "John X. Ãørçeč"' in metadata
        assert "Γαμα קּ 東 " in metadata
        assert "UTF-8 描述 説明" in metadata
  56. identifier `Γαμα` mixes scripts: Greek + Latin (in string/comment) (raw pass, confidence 0.40)

        assert 'Author-email: "John X. Ãørçeč"' in metadata
        assert "Γαμα קּ 東 " in metadata
        assert "UTF-8 描述 説明" in metadata
  57. info critical → info by review dynamic-import setuptools/tests/test_bdist_wheel.py:990:16

    `__import__` called on a non-literal expression (ast pass, confidence 0.70)

    
            return importlib.__import__(name, *args, **kwargs)
    
        with suppress(KeyError):
    likely benign LLM, confidence 0.90 Test code wraps importlib.__import__ for mocking.
    Reasoning

    A test helper wrapping importlib.__import__ to simulate import behavior, typical of test mocking. No malicious indicators in the snippet.

  58. info warn → info by review dynamic-execution setuptools/tests/test_editable_install.py:448:9

    `exec` called on a non-literal expression (ast pass, confidence 0.70)

            loc = {}
            exec(finder, loc, loc)  # noqa: S102 # exec is intentional here
            loc["install"]()
    likely benign LLM, confidence 0.90 Editable-install test execs generated finder code in a local namespace.
    Reasoning

    Test for editable installs execs a generated finder code with a local namespace, a standard pattern in the test suite; the exec is marked intentional.

  59. info warn → info by review dynamic-execution setuptools/wheel.py:250:64

    `eval` called on a non-literal expression (ast pass, confidence 0.70)

                        raw_req,
                        (req for req in reqs if for_extra(req) and eval(req, extra=extra)),
                    )
                )
    likely benign LLM, confidence 0.88 Wheel metadata code evaluates requirement markers with eval.
    Reasoning

    eval evaluates requirement environment markers for extras in wheel metadata handling; this is the expected marker-evaluation mechanism in setuptools, operating on requirement strings rather than hidden payloads.

Reproduce This Scan

Run disclude locally to verify. Add --format sarif for SARIF output. Add --llm (with an API key in the environment) to include the LLM review.

cargo install disclude --version 4.1.0
git clone https://github.com/pypa/setuptools setuptools && cd setuptools
git checkout v84.2.1
disclude scan --severity info --diff v84.2.0 .