PyPI / cffi / 2.1.1

cffi 2.1.1

Source
https://github.com/python-cffi/cffi
Artifact scanned
cffi-2.1.1.tar.gz (sdist · 518.4 KB · SHA-256 dd31f52ea108…)
Compared with
cffi-2.1.0.tar.gz (0 signals introduced since)
Files
204 scanned, 18 skipped (binary, oversized, or unrecognised)
Signals
0 critical · 0 warn · 31 info
Scan time
26.1 s
LLM review
Reviewed by claude-haiku-5-5 (Anthropic): 31 of 31 warn and critical signals; 31 demoted to info.
Disclude version
4.3.0
Scanned (UTC)
2026-10-22 23:24

Signals

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

  1. info warn → info by review dynamic-attribute demo/_curses.py:15:5

    reach-by-name through `globals` (ast pass, confidence 0.60)

    def _copy_to_globals(name):
        globals()[name] = getattr(lib, name)
    likely benign LLM, confidence 0.93 Helper copies named attributes from the cffi lib object into module globals.
    Reasoning

    This is cffi's standard pattern for exposing library symbols as module globals, a mechanical binding of the cffi-generated lib object to names. It is an ordinary dynamic-attribute lookup in a well-known FFI binding module, not hidden behavior.

  2. info warn → info by review dynamic-attribute demo/_curses.py:62:13

    reach-by-name through `globals` (ast pass, confidence 0.60)

                key_n = key_n.replace('(', '').replace(')', '')
                globals()[key_n] = key
    
    _setup()
    likely benign LLM, confidence 0.93 Assigns C constant values to module globals by name during curses binding setup.
    Reasoning

    Part of the same cffi binding setup that assigns constant names from the parsed C header into module globals. The key names come from the library's own declarations, so this is a legitimate binding technique.

  3. info warn → info by review dynamic-attribute demo/_curses.py:730:9

    reach-by-name through `globals` (ast pass, confidence 0.60)

        if len(ichar) == 1:
            globals()[name] = lib.acs_map[ord(ichar)]
        else:
            globals()[name] = globals()[ichar]
    likely benign LLM, confidence 0.93 Sets ACS curses symbols in module globals from lib.acs_map.
    Reasoning

    Populates ACS character constants into module globals from the curses library's acs_map, a routine binding step. Nothing here is externally driven or obfuscated.

  4. info warn → info by review dynamic-attribute demo/_curses.py:732:9

    reach-by-name through `globals` (ast pass, confidence 0.60)

        else:
            globals()[name] = globals()[ichar]
    likely benign LLM, confidence 0.92 Copies an existing global value under an alias name.
    Reasoning

    Aliases one global name to another within the same binding table; purely internal name mapping in a demo file.

  5. info warn → info by review dynamic-attribute demo/_curses.py:732:27

    reach-by-name through `globals` (ast pass, confidence 0.60)

        else:
            globals()[name] = globals()[ichar]
    likely benign LLM, confidence 0.92 Reads a global by name to alias it within the curses binding.
    Reasoning

    Same aliasing line as the previous finding, flagged for the globals() read. No externally controlled input reaches it.

  6. info critical → info by review dynamic-import demo/fastcsv.py:244:18

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

        try:
            module = __import__('_fastcsv_' + dialect_name)
        except ImportError:
            _make_ffi_from_dialect(dialect_name)
    likely benign LLM, confidence 0.80 Imports a dialect-specific generated CSV extension module, building it if missing.
    Reasoning

    The import name is a fixed prefix plus a dialect name, which is the cffi demo's way of loading a generated extension module, with a fallback to building it on ImportError. The dialect is a local parameter, not network or attacker-fetched input, so the risk is low, but the non-literal import is worth noting.

  7. info critical → info by review dynamic-import demo/fastcsv.py:247:18

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

            _make_ffi_from_dialect(dialect_name)
            module = __import__('_fastcsv_' + dialect_name)
        ffi, lib = module.ffi, module.lib
        #
    likely benign LLM, confidence 0.80 Retry import of the generated fastcsv dialect module after building it.
    Reasoning

    Same generated-module import as the sibling finding, reached after building the FFI for the dialect. Standard cffi demo code with no payload or remote fetch.

  8. info warn → info by review dynamic-execution setup.py:126:10

    `os.popen` called with a literal shell command (ast pass, confidence 0.70)

        # PKG_CONFIG_PATH = $(brew --prefix libffi)/lib/pkgconfig
        with os.popen('brew --prefix libffi') as brew_prefix_cmd:
            prefix = brew_prefix_cmd.read().strip()
        pkgconfig = os.path.join(prefix, 'lib', 'pkgconfig')
    likely benign LLM, confidence 0.90 Runs brew --prefix libffi at build time to find the libffi pkgconfig directory.
    Reasoning

    A fixed command queries the Homebrew libffi prefix so the build can locate pkg-config files. The command is a literal, and only its output is used to build a path. This is a standard build-time helper, though os.popen is a shell call.

  9. info warn → info by review dynamic-attribute src/c/_cffi_backend.c:4329:15

    `dlsym` called with a non-literal symbol name (ast pass, confidence 0.75)

        dlerror();   /* clear error condition */
        funcptr = dlsym(dlobj->dl_handle, funcname);
        if (funcptr == NULL) {
            const char *error = dlerror();
    likely benign LLM, confidence 0.93 Looks up a named function symbol in a dynamically loaded library for cffi.
    Reasoning

    dlsym with a caller-supplied name is the core mechanism of cffi's dlopen-based library access, where the user explicitly asks for a named symbol. It is the expected behavior of an FFI library, not an obfuscated payload.

  10. info warn → info by review dynamic-attribute src/c/_cffi_backend.c:4358:12

    `dlsym` called with a non-literal symbol name (ast pass, confidence 0.75)

        dlerror();   /* clear error condition */
        data = dlsym(dlobj->dl_handle, varname);
        if (data == NULL) {
            const char *error = dlerror();
    likely benign LLM, confidence 0.93 Resolves a named data symbol from a loaded shared library.
    Reasoning

    Same cffi dlsym mechanism used to resolve named global variables from a loaded library. Expected FFI behavior.

  11. info warn → info by review dynamic-attribute src/c/_cffi_backend.c:4386:12

    `dlsym` called with a non-literal symbol name (ast pass, confidence 0.75)

        dlerror();   /* clear error condition */
        data = dlsym(dlobj->dl_handle, varname);
        if (data == NULL) {
            const char *error = dlerror();
    likely benign LLM, confidence 0.93 Resolves a named data symbol in the cffi backend, with error handling.
    Reasoning

    Another variable lookup through dlsym in the cffi backend, identical in purpose to the neighboring finding. Legitimate FFI code.

  12. info warn → info by review dynamic-import src/c/_cffi_backend.c:4540:14

    `dlopen` called with a non-literal path (ast pass, confidence 0.75)

    
        handle = dlopen(filename_or_null, flags);
        PyMem_Free(filename_or_null);
    likely benign LLM, confidence 0.92 Opens a shared library at a user-provided path for cffi's dlopen interface.
    Reasoning

    dlopen on a path passed by the user is the purpose of cffi's dlopen API. The surrounding code frees the path buffer and handles errors, which is ordinary library loading.

  13. info warn → info by review dynamic-attribute src/c/cdlopen.c:15:15

    `dlsym` called with a non-literal symbol name (ast pass, confidence 0.75)

        dlerror();   /* clear error condition */
        address = dlsym(libhandle, symbol);
        if (address == NULL) {
            const char *error = dlerror();
    likely benign LLM, confidence 0.93 Looks up a requested symbol in an open library handle in cdlopen.
    Reasoning

    Standard dlsym lookup of a requested symbol with dlerror handling in cffi's cdlopen implementation. This is the intended feature of the library.

  14. info critical → info by review dynamic-execution src/c/test_c.py:80:20

    `eval` called on a constructed string (ast pass, confidence 0.80)

            def __add__(self, other):
                return eval('u'+repr(other).replace(r'\\u', r'\u')
                                           .replace(r'\\U', r'\U'))
        u = U()
    likely benign LLM, confidence 0.90 Test helper evaluates a constructed unicode literal string to check unicode handling.
    Reasoning

    This is a unicode-escape conversion inside a test helper's __add__, used to build u'...' literals from repr output. It is a known test idiom in cffi, and the eval input is derived only from the test's own values.

  15. info warn → info by review dynamic-execution src/cffi/_cffi_gen_src.py:53:5

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

        compiled = compile(source=pysrc, filename=filename, mode='exec')
        exec(compiled, globs, globs)
    likely benign LLM, confidence 0.90 Compiles and executes cffi-generated Python source to build a module namespace.
    Reasoning

    Compiles and executes generated cffi source code in a namespace during module generation. This is the expected code-generation step of cffi, operating on the project's own generated source.

  16. info warn → info by review dynamic-execution src/cffi/api.py:770:9

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

            #
            compile(pysource, "cffi_init", "exec")
            #
            self._embedding = pysource
    likely benign LLM, confidence 0.90 Syntax-checks generated embedding initialization source in cffi's API.
    Reasoning

    Compiles the generated embedding init source to check it is valid Python before storing it. This is a syntax check on cffi's own generated code, not execution of untrusted input.

  17. info warn → info by review dynamic-attribute src/cffi/cffi_opcode.py:180:35

    reach-by-name through `globals` (ast pass, confidence 0.60)

    
    G_FLAGS = dict([('_CFFI_' + _key, globals()[_key])
                    for _key in ['F_UNION', 'F_CHECK_FIELDS', 'F_PACKED',
                                 'F_EXTERNAL', 'F_OPAQUE']])
    likely benign LLM, confidence 0.93 Builds a flag dictionary from module-level constants using globals() lookups.
    Reasoning

    Builds the G_FLAGS dictionary by reading module-level constants via globals() with fixed key names. This is a plain constant lookup in the cffi opcode table and has no obfuscation or external input.

  18. info warn → info by review dynamic-execution src/cffi/recompiler.py:80:17

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

        def as_python_expr(self):
            flags = eval(self.flags, G_FLAGS)
            fields_expr = [c_field.as_field_python_expr()
                           for c_field in self.c_fields]
    likely benign LLM, confidence 0.85 Evaluates a flags expression while generating Python source for a C struct.
    Reasoning

    eval of self.flags with a fixed G_FLAGS namespace is cffi's own code generation for C flag expressions. Its input comes from the library's declarations, not untrusted data.

  19. info warn → info by review dynamic-execution src/cffi/setuptools_ext.py:25:12

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

        src += '\n'      # Python 2.6 compatibility
        code = compile(src, filename, 'exec')
        exec(code, glob, glob)
    likely benign LLM, confidence 0.80 Compiles Python source loaded from a file in a setuptools helper.
    Reasoning

    compile of source read from a file the build system is already processing, executed into a namespace. This is the standard pattern for loading a generated or configuration Python file in a build helper.

  20. info warn → info by review dynamic-execution src/cffi/setuptools_ext.py:26:5

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

        code = compile(src, filename, 'exec')
        exec(code, glob, glob)
    likely benign LLM, confidence 0.80 Executes compiled build-helper source into a namespace.
    Reasoning

    Executes the code compiled in the preceding line, within the same build-time helper. The source is the project's own file, not attacker-controlled input.

  21. info critical → info by review dynamic-import src/cffi/verifier.py:151:27

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

                    try:
                        pkg = __import__(self.ext_package, None, None, ['__doc__'])
                    except ImportError:
                        return      # cannot import the package itself, give up
    likely benign LLM, confidence 0.85 Dynamically imports the cffi-generated extension package.
    Reasoning

    Imports the generated extension package named by self.ext_package, with a fallback on ImportError. This is the expected way cffi loads its own compiled module, though the critical severity reflects the dynamic import rather than any malicious payload.

  22. info warn → info by review frame-introspection src/cffi/verifier.py:252:16

    `sys._getframe` — call-stack frame introspection (ast pass, confidence 0.70)

            return result
        filename = sys._getframe(2).f_code.co_filename
        return os.path.abspath(os.path.join(os.path.dirname(filename),
                               '__pycache__'))
    likely benign LLM, confidence 0.90 Reads the caller's filename via frame introspection to build a cache path.
    Reasoning

    sys._getframe(2) reads the calling module's filename to locate a __pycache__ directory. This is a routine way to find the caller's location and has no exfiltration or evasion behaviour.

  23. info critical → info by review dynamic-execution testing/cffi0/test_ownlib.py:160:17

    `subprocess.check_call` with `shell=True` and a non-literal command — runtime-determined shell invocation (ast pass, confidence 0.85)

                    cmd = f'"{vcvarsall}" {arch} & cl.exe testownlib.c /LD /Fetestownlib.dll'
                    subprocess.check_call(cmd, cwd = str(udir), shell=True)
                    os.rename(f"{udir}\\testownlib.dll", dll_path)
                    cls.module = dll_path
    likely benign LLM, confidence 0.85 Test fixture runs MSVC via a shell to compile a test DLL.
    Reasoning

    In a test fixture, a shell=True command invokes vcvarsall and cl.exe to build a test DLL. The interpolated values are local paths and architecture strings set by the test, so this is ordinary build scripting.

  24. info critical → info by review dynamic-execution testing/cffi0/test_ownlib.py:176:13

    `subprocess.check_call` with `shell=True` and a non-literal command — runtime-determined shell invocation (ast pass, confidence 0.85)

                    encoded = str(unicode_name)
                subprocess.check_call(
                    "cc testownlib.c -shared -fPIC -o '%s.so'" % (encoded,),
                    cwd=str(udir), shell=True)
    likely benign LLM, confidence 0.80 Test fixture compiles a shared library via a shell command with a test-controlled name.
    Reasoning

    A shell=True cc command in a test fixture, with a filename derived from a unicode test name. Shell metacharacters could in principle appear, but the input is a fixed test constant, so the risk is confined to the test run.

  25. info warn → info by review dynamic-execution testing/cffi0/test_verify.py:1784:26

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

            assert lib.AA == 0
            assert lib.BB == eval(hidden_value.replace('U', '').replace('L', ''))
            assert ffi.sizeof("enum foo_e") == expected_size
            assert int(ffi.cast("enum foo_e", -1)) == expected_minus1
    likely benign LLM, confidence 0.85 Test evaluates a C integer literal to compare against a cffi enum value.
    Reasoning

    eval applied to a hidden_value string from a test table, with U and L suffixes stripped, to check enum constant values. The input is a fixed test constant.

  26. info critical → info by review dynamic-import testing/cffi0/test_zintegration.py:62:22

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

            for module in modules:
                target = __import__(module, None, None, [])
                if not hasattr(target, '__file__'):   # for _cffi_backend on pypy
                    continue
    likely benign LLM, confidence 0.85 Integration test imports a fixed list of modules by name.
    Reasoning

    Imports a list of cffi modules by name in an integration test, skipping those without __file__. This is an ordinary test loop over fixed module names.

  27. info critical → info by review dynamic-execution testing/cffi1/test_cffi_binary.py:11:9

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

            pytest.skip("linux-only")
        g = os.popen("objdump -T '%s'" % _cffi_backend.__file__, 'r')
        for line in g:
            if not line.startswith('0'):
    likely benign LLM, confidence 0.90 Linux-only test inspects the backend's dynamic symbols with objdump.
    Reasoning

    Test runs objdump via os.popen on the path of the cffi backend to check exported symbols. The path is the module's own file and the test is skipped off Linux.

  28. info critical → info by review dynamic-execution testing/cffi1/test_dlopen_unicode_literals.py:9:1

    `exec` at module scope (runs on import) on a non-literal expression (ast pass, confidence 0.80)

    
    exec(compile(s, filename='test_dlopen.py', mode='exec'))
    likely benign LLM, confidence 0.75 Test module execs a companion test file's source at import time.
    Reasoning

    Module-level exec of compiled source named test_dlopen.py, presumably the sibling test file, run under unicode_literals. Unusual, but it is a fixed test-suite construct with no network, decoding or persistence.

  29. info critical → info by review dynamic-execution testing/cffi1/test_dlopen_unicode_literals.py:9:6

    `compile` at module scope (runs on import) on a non-literal expression (ast pass, confidence 0.80)

    
    exec(compile(s, filename='test_dlopen.py', mode='exec'))
    likely benign LLM, confidence 0.75 Compiles a companion test file's source at module import.
    Reasoning

    The compile half of the same module-level exec, building code from a fixed companion test file. The same reasoning applies: it is a test-suite construct without malicious indicators.

  30. info warn → info by review dynamic-execution testing/cffi1/test_verify1.py:1752:26

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

            assert lib.AA == 0
            assert lib.BB == eval(hidden_value.replace('U', '').replace('L', ''))
            assert ffi.sizeof("enum foo_e") == expected_size
            if sys.platform != 'win32':
    likely benign LLM, confidence 0.85 Test evaluates a C integer literal to compare against a cffi enum value.
    Reasoning

    Same pattern as the cffi0 test: eval of a C integer literal string from a test table to check enum values. Fixed test input, no attacker-controlled data.

  31. info critical → info by review dynamic-execution testing/support.py:9:20

    `eval` called on a constructed string (ast pass, confidence 0.80)

            def __add__(self, other):
                return eval('u'+repr(other).replace(r'\\u', r'\u')
                                           .replace(r'\\U', r'\U'))
        u = U()
    likely benign LLM, confidence 0.80 Test helper evals a repr-derived string to build a unicode literal.
    Reasoning

    A test helper evals a string built from repr() with escape rewriting to create Python 2/3 unicode literals. The input is the test's own value, so eval is used for literal construction, not as an injection path.

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 cffi-2.1.1.tar.gz https://files.pythonhosted.org/packages/9e/ef/008a1939e372c06329a3fce4279c02f328488f3526744906eeec3da7ad5f/cffi-2.1.1.tar.gz
printf '%s  %s\n' dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be cffi-2.1.1.tar.gz | shasum -a 256 -c -
mkdir pkg && tar -xzf cffi-2.1.1.tar.gz -C pkg --strip-components 1
disclude scan --untrusted --severity info pkg