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
25.4 s
LLM review
Reviewed by claude-haiku-5-5 (Anthropic): 31 of 31 warn and critical signals; 31 demoted to info.
Disclude version
4.1.0
Scanned (UTC)
2026-10-21 20:26

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.90 Helper that exposes ncurses library symbols as module-level names via globals().
    Reasoning

    Copies curses library symbols into module globals by name, a common binding pattern for a C-binding wrapper. The name comes from the library's own symbol list, 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.90 Loop that exports parsed curses key constants into module globals.
    Reasoning

    Assigns parsed constant names into globals() while setting up the curses binding. This is a standard dynamic-export pattern in cffi-generated wrapper code.

  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.90 Populates ACS line-drawing constants in the curses demo module via globals().
    Reasoning

    Maps ACS character names to library values through globals(). It is internal binding setup with no external input or network activity.

  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.90 Copies an existing global constant to another name in the curses demo.
    Reasoning

    Aliases one global name to another already defined in the module. This is ordinary wrapper setup code.

  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.90 Reads an existing module global to assign to another name in the curses demo.
    Reasoning

    Same globals() aliasing expression as the adjacent finding, reporting the read side. It is benign module setup.

  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 Dynamic import of a per-dialect cffi-generated fastcsv extension module.
    Reasoning

    Imports a generated extension module whose name is a fixed prefix plus the dialect name. The import is wrapped in a fallback that builds the module on demand. The dialect is a developer-chosen identifier, so this is a plugin-loading pattern rather than a dropper, though the non-literal argument 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 Second __import__ of the generated fastcsv dialect module after building it.
    Reasoning

    Retry import of the same generated module after the fallback builds it. The surrounding code is ordinary build-on-demand logic for a demo.

  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 Build-time call to Homebrew to find the libffi installation prefix.
    Reasoning

    Runs a fixed literal command, brew --prefix libffi, to locate the libffi pkg-config path during macOS build setup. Nothing from the environment is interpolated into the command.

  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.85 Looks up a function symbol by name in a dynamically loaded library for cffi.
    Reasoning

    dlsym with a caller-supplied symbol name is the core purpose of cffi's ffi.dlopen-based symbol lookup, and the code checks for NULL and clears errors correctly.

  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.85 Resolves a named data symbol from a dynamically loaded library.
    Reasoning

    dlsym for a named variable is the expected cffi mechanism for accessing library globals. It is not hidden or obfuscated.

  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.85 Second dlsym call resolving a named variable from a loaded library.
    Reasoning

    Same variable-symbol lookup pattern as the neighboring finding, with standard error handling. This is expected FFI functionality.

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

    dlopen with a path supplied by the user is the designed behavior of cffi's dlopen. The path is handled and freed properly, so it is not a hidden loader.

  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.85 Symbol lookup in cffi's cdlopen implementation.
    Reasoning

    dlsym of a caller-supplied symbol in the cdlopen helper is the normal foreign-function interface mechanism, with NULL checking and dlerror handling.

  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 that evaluates a repr-derived unicode literal to exercise escape handling.
    Reasoning

    Eval rebuilds a unicode literal from repr output inside a test helper to check unicode escape handling. Input is a test value, not attacker-controlled data, so this is test scaffolding.

  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.85 Executes cffi-generated Python source during code generation.
    Reasoning

    Compiles and execs generated source at build or codegen time, which is how cffi builds its modules. The source comes from the project's own code generator.

  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.85 Compiles the generated embedding initialization source in cffi's API.
    Reasoning

    Compiles embedding init source produced by cffi's embedding API. It only checks syntax and stores the source, with no execution of untrusted content.

  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.90 Collects cffi opcode flag constants into a dictionary through globals() lookups.
    Reasoning

    Builds a flag dictionary from a fixed list of module-level constant names via globals(). This is a plain constants-lookup idiom with no hidden behavior.

  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.90 eval of a flags expression inside cffi's recompiler when rendering field expressions.
    Reasoning

    eval runs over self.flags with a fixed G_FLAGS namespace while generating Python expressions for cffi's own declarations. This is internal code-generation logic, not attacker-controlled input.

  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.85 compile and exec of build-script source inside cffi's setuptools extension loader.
    Reasoning

    The source is read from a generated or build-time file and compiled and exec'd into a module namespace. This is the standard way setuptools helpers load cffi build scripts. The surrounding code shows no network fetch or encoded payload.

  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.85 exec of compiled build-script code within cffi's setuptools extension.
    Reasoning

    This is the exec half of the same compile-and-run pattern for loading a Python file into a namespace. Context shows it is a build helper, not a dropper.

  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 own generated extension package in the verifier.
    Reasoning

    __import__ loads the package cffi itself generated (self.ext_package), and the ImportError branch simply gives up. Dynamic import of a known local extension module is expected here.

  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 Caller-frame lookup to compute a __pycache__ path for generated modules.
    Reasoning

    sys._getframe(2) is used to find the caller's file location so a __pycache__ directory can be placed next to it. This is a known idiom and does not hide behavior.

  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 helper that shells out to MSVC to build a test DLL.
    Reasoning

    This is a Windows test fixture that runs the MSVC compiler via shell=True. The command is built from the vcvarsall path and architecture, and it compiles a local test C file. It is test scaffolding, not an attack.

  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 helper that shells out to cc to build a test shared library.
    Reasoning

    The shell command compiles a local test C source into a shared library. The only interpolated value is a test-provided encoded filename, so the risk is limited to the test environment.

  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 eval of a test constant to check enum value handling.
    Reasoning

    eval runs a hidden_value string from a C-level test with 'U' and 'L' suffixes stripped. This is a fixed constant check for enum values in a test file.

  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.90 Dynamic import of a fixed list of cffi modules in an integration test.
    Reasoning

    __import__ iterates over a fixed list of cffi module names to check their presence in the test suite. This is a standard test pattern.

  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 that inspects the cffi backend's exported symbols with objdump.
    Reasoning

    os.popen runs objdump on the cffi backend's own __file__ path to check exported symbols. It is a Linux-only test with no external input.

  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.70 Module-level exec of test source compiled with unicode literals.
    Reasoning

    A module-level exec of compiled source is unusual, but the snippet fits a unicode-literals test that runs a bundled test script. The source comes from the test file itself, not from an external or obfuscated source. Some residual doubt remains because the origin of s is not shown.

  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.70 Module-level compile of the test_dlopen source for unicode-literal testing.
    Reasoning

    This is the compile half of the same module-level exec. It runs test source named test_dlopen.py, which matches the test's purpose.

  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 eval of a test constant to validate enum sizes in a verify test.
    Reasoning

    This is the same enum-value eval in the cffi1 verify test, applied to a fixed hidden_value constant with suffixes removed. It is a test assertion.

  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 that uses eval to create a Python 2/3 compatible unicode literal.
    Reasoning

    eval builds a unicode literal from repr() output to emulate u'' literals on older Pythons. The input is a fixed test helper value, so the constructed string has no hostile source.

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