PyPI / werkzeug / 3.1.9

werkzeug 3.1.9

Source
https://github.com/pallets/werkzeug
Tag scanned
3.1.9
Compared with
3.1.8 (0 signals introduced since)
Files
188 scanned, 87 skipped (binary, oversized, or unrecognised)
Signals
0 critical · 0 warn · 6 info
Scan time
7.1 s
LLM review
Reviewed by claude-haiku-5-5 (Anthropic): 6 of 6 warn and critical signals; 6 demoted to info.
Disclude version
4.0.0
Scanned (UTC)
2026-10-14 18:39

Signals

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

  1. info warn → info by review dynamic-execution src/werkzeug/debug/console.py:177:13

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

            try:
                exec(code, self.locals)
            except Exception:
                self.showtraceback()
    likely benign LLM, confidence 0.85 Werkzeug's debug console runs entered Python code in the debugger's locals via exec.
    Reasoning

    This is the interactive console of Werkzeug's debugger. Executing user-entered code in the debugger's namespace is its purpose. The debugger is gated by a PIN and is meant for development, so the exec is a documented feature rather than hidden obfuscation or a dropper. The residual doubt is that it is still arbitrary code execution if the debugger is exposed.

  2. info warn → info by review frame-introspection src/werkzeug/debug/repr.py:49:30

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

        if obj is missing:
            rv = gen.dump_locals(sys._getframe(1).f_locals)
        else:
            rv = gen.dump_object(obj)
    likely benign LLM, confidence 0.90 The debug dump helper reads the caller's frame locals to display them when no object is given.
    Reasoning

    sys._getframe(1).f_locals is used by the debug helper dump() to show the caller's local variables when no object is passed. This is a standard introspection feature of a debugging utility and does not hide behaviour or exfiltrate data.

  3. info warn → info by review dynamic-execution src/werkzeug/routing/rules.py:736:9

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

            locs: dict[str, t.Any] = {}
            exec(code, globs, locs)
            return locs[name]  # type: ignore
    likely benign LLM, confidence 0.85 The routing rules module execs compiled matcher code generated internally by the library.
    Reasoning

    This exec runs a function body that Werkzeug's routing rule compiler generates from the library's own rule templates. The code is built from trusted internal converter definitions, not from untrusted input. This is a known code-generation pattern for performance.

  4. info warn → info by review dynamic-execution src/werkzeug/routing/rules.py:836:16

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

    
            code = compile(module, "<werkzeug routing>", "exec")
            return self._get_func_code(code, func_ast.name)
    likely benign LLM, confidence 0.85 The routing module compiles internally generated AST into a code object for the matcher.
    Reasoning

    compile() here turns an AST module that Werkzeug generated internally into a code object, which is then exec'd by the adjacent code. The source is internal and trusted, so this is ordinary runtime code generation with no sign of attacker-controlled input.

  5. info critical → info by review dynamic-import src/werkzeug/utils.py:589:13

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

            try:
                __import__(import_name)
            except ImportError:
                if "." not in import_name:
    likely benign LLM, confidence 0.92 Werkzeug's import_string utility performs a dynamic __import__ of a caller-specified module name.
    Reasoning

    This is the import_string helper, which imports a module by dotted name. Dynamic import with a caller-supplied name is its documented purpose. The try/except ImportError fallback is normal for a utility like this. The critical severity reflects the signal, not real malicious behaviour.

  6. info critical → info by review dynamic-import src/werkzeug/utils.py:597:18

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

            module_name, obj_name = import_name.rsplit(".", 1)
            module = __import__(module_name, globals(), locals(), [obj_name])
            try:
                return getattr(module, obj_name)
    likely benign LLM, confidence 0.92 The same import_string helper dynamically imports the module part of a dotted path and fetches the attribute.
    Reasoning

    This is the second __import__ in the same import_string helper. It splits a dotted name into module and attribute, then imports the module, which is the standard way to resolve an object path from a string. It is the expected behaviour of this utility.

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/pallets/werkzeug werkzeug && cd werkzeug
git checkout 3.1.9
disclude scan --severity info --diff 3.1.8 .