PyPI / setuptools / 84.2.1

setuptools 84.2.1

Source
https://github.com/pypa/setuptools
Artifact scanned
setuptools-84.2.1-py3-none-any.whl (wheel · 855.7 KB · SHA-256 ca140b45d532…)
Compared with
setuptools-84.2.0-py3-none-any.whl (0 signals introduced since)
Files
246 scanned, 102 skipped (binary, oversized, or unrecognised)
Signals
0 critical · 1 warn · 40 info
Scan time
21.1 s
LLM review
Reviewed by claude-haiku-5-5 (Anthropic): 20 of 20 warn and critical signals; 19 demoted to info.
Disclude version
4.3.0
Scanned (UTC)
2026-10-22 23:27

Signals

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

  1. warn 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.60 Evaluates requirement marker expressions with extra context while converting wheel metadata to requirements.
    Reasoning

    The call eval(req, extra=extra) inside a generator is unusual, since real marker evaluation usually goes through the packaging marker API. The snippet is short and this is likely a local helper or a truncated view of marker evaluation on requirement strings. Nothing shows decoding or fetching external code, so benign is the best reading, but the snippet alone leaves some doubt.

  2. info warn → info by review pth-executable-line distutils-precedence.pth:1:1

    `.pth` line that Python's `site` executes at every interpreter start (ast pass, confidence 0.70)

    import os; var = 'SETUPTOOLS_USE_DISTUTILS'; enabled = os.environ.get(var, 'local') == 'local'; enabled and __import__('_distutils_hack').add_shim();
    likely benign LLM, confidence 0.85 Standard setuptools distutils-precedence .pth line that enables the _distutils_hack shim at interpreter start.
    Reasoning

    This is the standard setuptools .pth shim. It only checks SETUPTOOLS_USE_DISTUTILS and calls _distutils_hack.add_shim(), with no decoding, network access or process spawning, so it matches the legitimate pattern for this file.

  3. 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.80 Runs a constructed rpm query command during RPM build packaging.
    Reasoning

    os.popen is used to run an rpm query command built from the packaging configuration. The command is runtime-determined but comes from build metadata, which is expected for an RPM packaging command.

  4. 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 Fixed-literal rpm --eval call that reads a macro during RPM packaging.
    Reasoning

    The command is a fixed literal 'rpm --eval %{__os_install_post}' used to read the vendor macro. It is a standard RPM-building step with no untrusted input.

  5. 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.90 eval of a hardcoded tuple string listing valid library types.
    Reasoning

    eval is applied to a fixed string literal of library type names defined a few lines above. No external input reaches it, so it is a benign idiom.

  6. 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 Executes the project's setup script as part of the distutils setup() runner.
    Reasoning

    This is the distutils setup-script runner, which executes the user's setup.py by design. The comment notes this, and the exec is the expected behavior of the tool.

  7. 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 Dynamic import of a distutils command module by name.
    Reasoning

    __import__ loads a command or module by name during option and command resolution. The module names come from distutils' own command registry, which is normal plugin loading.

  8. 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 source files during installation.
    Reasoning

    This is the standard byte-compilation helper that compiles Python source files during install. The compile call is the expected operation in that context.

  9. 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.90 Streaming zlib decompression within the vendored tarfile implementation.
    Reasoning

    This is the backported tarfile module, which uses zlib decompressobj to read compressed tar streams. Decompression is its core function, not a hidden payload.

  10. 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.70 eval of a caller-supplied expression in a vendored jaraco functools helper.
    Reasoning

    eval runs a 'use' argument in a helper that converts a value through an expression. The vendored jaraco helper is generic, but the snippet doesn't show where 'use' comes from, so some doubt remains.

  11. 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 Compiles a license expression purely to validate its syntax.
    Reasoning

    compile with mode 'eval' is used only to syntax-check a license expression and catch SyntaxError. It compiles without executing, so it is a validation step.

  12. 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.90 Executes setup.py code inside the PEP 517 build backend.
    Reasoning

    This is the PEP 517 build backend, which executes the project's setup.py as part of building. Running setup code is the purpose of this module.

  13. 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 Loads a code object from a module file while building an egg.
    Reasoning

    marshal.load reads bytecode from an existing module file during egg construction, such as reading a .pyc header or code object for analysis. This is normal for the egg-building code path.

  14. 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.85 Inspects the call stack to identify the setup-script caller during install.
    Reasoning

    inspect.getouterframes is used to find the calling setup script frame so install can reason about its caller. Frame introspection like this is a known setuptools pattern and has no malicious effect.

  15. 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': 
  16. 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
  17. 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.
  18. 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
  19. 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',
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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>`_.']
  27. 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':
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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 Loads module bytecode from a file for dependency analysis.
    Reasoning

    marshal.load reads the bytecode of a module being inspected for dependency detection, matching the code-object loading that setuptools' depends module does by design.

  37. 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.85 Compiles a module's source file (and reads marshalled bytecode) to inspect a dependency's contents.
    Reasoning

    The snippet is the standard setuptools pattern for inspecting a module's source or bytecode. Reading a file and compiling it with a path is needed to check a module's contents without importing it. The marshal import matches the bytecode branch of the same function. No network, shell, or payload-decoding behavior is visible.

  38. 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.85 Calls compile() on source read from a module file during dependency inspection.
    Reasoning

    compile() on file contents read from a path the library itself located is ordinary for a module-introspection utility. The source comes from the local filesystem and nothing is decoded or executed beyond compilation.

  39. 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.85 Probes whether the optional Cython build_ext module can be imported, falling back to False on error.
    Reasoning

    The __import__ call sits inside a try/except that returns False on failure. It is a deliberate availability check for the optional Cython build_ext, using a fixed module name from the library and the comment states the intent. The critical severity reflects the dynamic-import signal, not any malicious behavior.

  40. 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.90 Compiles a setup script's source for execution in the launcher namespace.
    Reasoning

    This is the setuptools launcher that runs a setup script in a supplied namespace, which is its documented purpose. The explicit noqa comment marks the exec as intentional. Script newline normalization is a compatibility step, not obfuscation.

  41. 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.90 Executes the compiled setup script via the launcher, as intended.
    Reasoning

    Executing a setup script in a controlled namespace is the launcher's job, and the code is annotated as intentional. Nothing in the snippet decodes an obfuscated payload or reaches the network.

Reproduce This Scan

Run disclude locally to verify. These commands scan the published file; the "new in this release" marks come from comparing it with the previous release's file, which they don't repeat. 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.3.0
curl -fsSL -o setuptools-84.2.1-py3-none-any.whl https://files.pythonhosted.org/packages/ef/55/6dc6d6bb953bdfc52519a0059592c52b6e3b8ad51cf8256dffceb55b0610/setuptools-84.2.1-py3-none-any.whl
printf '%s  %s\n' ca140b45d5325a9478f880e351484f62fef15f336462e449ca0c03c873d589c2 setuptools-84.2.1-py3-none-any.whl | shasum -a 256 -c -
unzip -q setuptools-84.2.1-py3-none-any.whl -d pkg
disclude scan --untrusted --severity info pkg