PyPI / cffi / 2.1.1

cffi 2.1.1

Source
https://github.com/python-cffi/cffi
Tag scanned
v2.1.1
Compared with
v2.1.0 (0 signals introduced since)
Files
201 scanned, 19 skipped (binary, oversized, or unrecognised)
Signals
0 critical · 0 warn · 31 info
Scan time
23.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.0.0
Scanned (UTC)
2026-10-14 18:09

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.92 Copies C library symbols into the module globals by name.
    Reasoning

    This is cffi's standard pattern for exposing C library symbols as module attributes by name. The names come from the library's own header, not untrusted input.

  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.92 Assigns parsed curses constant names into module globals.
    Reasoning

    Same cffi binding-setup idiom: key names parsed from the curses header are assigned into module globals. No external input or hidden execution is involved.

  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.92 Sets ACS character constants on the module from the C library map.
    Reasoning

    Populates ACS line-drawing constants from lib.acs_map into globals, a routine binding-generation pattern for a fixed C library.

  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 Aliases one curses constant name to another via globals.
    Reasoning

    Aliases one module global to another by name, a benign reshuffling of constants generated from the C header.

  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 module global by computed name during constant aliasing.
    Reasoning

    Reads a global by a computed key within the same constant-aliasing logic. No hidden payload or external input is involved.

  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.75 Imports a generated fastcsv extension module chosen by dialect name.
    Reasoning

    Dynamic __import__ builds a module name from a fixed prefix and a dialect name. That is a cffi demo loading its own compiled extension, but the name is not validated, so it is worth a minor note.

  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.75 Imports the generated fastcsv extension after building its FFI.
    Reasoning

    Same dynamic import of the generated _fastcsv_ extension after building the FFI from the dialect. Consistent with the demo's build flow.

  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.93 Queries brew for the libffi prefix during setup.
    Reasoning

    Runs a fixed shell command to locate libffi for pkg-config during build setup. The command is a literal with no interpolated input.

  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.90 Resolves a function symbol from a dlopen handle by name.
    Reasoning

    dlsym with a caller-supplied name is the normal FFI mechanism for resolving functions in a loaded library. It is the core purpose of the backend, not hidden behavior.

  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.90 Resolves a global variable symbol from a loaded library.
    Reasoning

    dlsym resolving a variable name in a loaded library is the expected behavior of cffi's dlopen support.

  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.90 Resolves another variable symbol from a loaded library.
    Reasoning

    Same expected dlsym lookup of a variable symbol in a dlopen'd library, with error handling around it.

  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.90 Opens a shared library at a caller-specified path.
    Reasoning

    dlopen on a user-supplied path is the advertised purpose of ffi.dlopen, a library loader for FFI users. It is not covert.

  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.90 Looks up a named symbol in an opened shared library.
    Reasoning

    dlsym of a symbol name inside cdlopen's library-loading path is standard FFI functionality with error checks.

  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.85 Test helper evals a constructed unicode literal string.
    Reasoning

    eval on a repr-derived string is in a test helper that converts Python 2 unicode literal forms. The input is controlled by the test, so this is not an exploitable path.

  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.92 exec of compiled cffi-generated source into a globals dict.
    Reasoning

    This is cffi's generator helper compiling and executing its own generated Python source in a namespace. Dynamic exec is the library's core mechanism, and the snippet shows no obfuscation or external payload.

  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.92 Syntax-check compile of cffi embedding init source.
    Reasoning

    The compile call only checks generated embedding source for syntax (filename 'cffi_init'), and the result is stored as _embedding. This is a legitimate validation step in the cffi API.

  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']])
    dismissed LLM, confidence 0.93 Builds a flags dict from fixed constant names via globals().
    Reasoning

    Reading module flags by name from globals() over a fixed list of F_* constants is a plain, bounded lookup. Nothing here is attacker-controlled.

  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.92 eval of internal flag expression in the recompiler.
    Reasoning

    eval of self.flags with a restricted G_FLAGS namespace in cffi's code generator. The flags are internal integer-expression strings produced by the library, so this is routine.

  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.90 compile of build helper source in setuptools_ext.
    Reasoning

    Compiles and execs the setuptools helper's own source (with a Python 2.6 compatibility comment) to load a build-time module. This is ordinary build tooling inside the cffi package.

  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.90 exec of the compiled build helper source.
    Reasoning

    Same pattern as the preceding compile: executing the helper module's own source during the build. The snippet shows no decoding, network access, or external 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.90 Dynamic import of cffi's extension package with ImportError fallback.
    Reasoning

    Dynamic __import__ of self.ext_package, wrapped in an ImportError handler, is the standard way cffi locates its generated extension package. The name comes from the library's own configuration.

  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 Frame introspection to find the caller's directory for __pycache__.
    Reasoning

    Reads the caller's filename via sys._getframe(2) to locate a __pycache__ directory. This is a common, benign path-resolution idiom.

  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 invoking the MSVC compiler through the shell.
    Reasoning

    This is a test fixture that builds a DLL with cl.exe via shell=True. The command interpolates a vcvarsall path and architecture from the test environment, not untrusted input. It is a shell call, but within a test harness.

  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.85 Test fixture compiling a unicode-named shared library via shell.
    Reasoning

    Test fixture compiling a shared library with cc, using a name derived from the test's unicode filename. shell=True with interpolated test paths is poor hygiene, but the input is test-controlled and the code is benign.

  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.93 Test evaluates a C integer literal to compare against a cffi enum value.
    Reasoning

    eval is applied to a hidden_value string from the test after stripping U and L integer suffixes. It is a test-only computation of an expected constant, not runtime attacker input.

  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.93 Test-time dynamic import of listed cffi modules.
    Reasoning

    Dynamic import of a list of module names in an integration test, with a __file__ check for PyPy. This is ordinary test scaffolding.

  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 Test helper shelling out to objdump on the cffi backend library to list dynamic symbols.
    Reasoning

    os.popen is called with a command built from the cffi backend's __file__ path inside a test that runs objdump to inspect exported symbols. The interpolated value is a local module path, not attacker-controlled input, and the test is linux-only. This is ordinary test tooling.

  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.90 Test module that execs a companion test script's source at import time.
    Reasoning

    This is a test that compiles and executes a dlopen test source file read from the repository. The exec is a standard pattern for running generated or companion test code, and the file is part of the project's own test suite. The flag is a generic AST match on exec, without evidence of hostile input.

  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.85 compile() of a test fixture source string, paired with the exec above.
    Reasoning

    This is the compile call feeding the exec above. The source string s is a project test fixture, and the filename is a fixed literal. Same benign test-harness pattern as the paired exec finding.

  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 evaluating a C-literal integer string to compare against a compiled constant.
    Reasoning

    eval is applied to a hidden_value string from the test, with 'U' and 'L' suffixes stripped so C integer literals become Python ints. This is a test computing an expected constant from C-style literals. The input is test-controlled, not external.

  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.85 Test support helper using eval to build a unicode literal from repr output for Python 2/3 compatibility.
    Reasoning

    eval is applied to a string built from repr() of a value with replacements to produce unicode escapes. This is a known idiom for constructing unicode literals in a Python 2/3 compatibility test helper. The input is a test-generated value, not untrusted data.

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.0.0
git clone https://github.com/python-cffi/cffi cffi && cd cffi
git checkout v2.1.1
disclude scan --severity info --diff v2.1.0 .