PyPI / pygments / 2.21.0

pygments 2.21.0

Source
https://github.com/pygments/pygments
Artifact scanned
pygments-2.21.0-py3-none-any.whl (wheel · 1.2 MB · SHA-256 2363c69b61c4…)
Compared with
pygments-2.20.0-py3-none-any.whl (0 signals introduced since)
Files
344 scanned, 5 skipped (binary, oversized, or unrecognised)
Signals
0 critical · 0 warn · 37 info
Scan time
15.1 s
LLM review
Reviewed by claude-haiku-5-5 (Anthropic): 16 of 16 warn and critical signals; 16 demoted to info.
Disclude version
4.3.0
Scanned (UTC)
2026-10-22 23:33

Signals

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

  1. info critical → info by review dynamic-import pygments/formatters/__init__.py:38:11

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

        """Load a formatter (and all others in the module too)."""
        mod = __import__(module_name, None, None, ['__all__'])
        for formatter_name in mod.__all__:
            cls = getattr(mod, formatter_name)
    likely benign LLM, confidence 0.93 Dynamic __import__ of a built-in formatter submodule by name.
    Reasoning

    This is Pygments' standard plugin loader. module_name is an internal module path from a fixed mapping of built-in formatters, not attacker input, and the code only reads __all__ and getattr's classes from it. Dynamic import is the expected mechanism here.

  2. info warn → info by review dynamic-execution pygments/formatters/__init__.py:103:13

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

            with open(filename, 'rb') as f:
                exec(f.read(), custom_namespace)
            # Retrieve the class `formattername` from that namespace
            if formattername not in custom_namespace:
    likely benign LLM, confidence 0.85 exec of a user-supplied formatter plugin file into a namespace.
    Reasoning

    This is the load_formatter_from_file feature, which execs a user-specified Python file to get a custom formatter class. Executing a file the user explicitly names is the documented purpose. It is not a hidden payload and the file is not fetched from a remote source.

  3. info critical → info by review dynamic-import pygments/lexers/__init__.py:45:11

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

        """Load a lexer (and all others in the module too)."""
        mod = __import__(module_name, None, None, ['__all__'])
        for lexer_name in mod.__all__:
            cls = getattr(mod, lexer_name)
    likely benign LLM, confidence 0.93 Dynamic __import__ of a built-in lexer module by name.
    Reasoning

    Standard Pygments lexer loader that imports a built-in lexer module by its internal name and reads its __all__. The name comes from a fixed internal mapping, so this is the expected pattern.

  4. info warn → info by review dynamic-execution pygments/lexers/__init__.py:154:13

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

            with open(filename, 'rb') as f:
                exec(f.read(), custom_namespace)
            # Retrieve the class `lexername` from that namespace
            if lexername not in custom_namespace:
    likely benign LLM, confidence 0.85 exec of a user-supplied lexer plugin file into a namespace.
    Reasoning

    This is the load_lexer_from_file feature, which execs a user-specified lexer plugin file. Running a file the caller explicitly points to is the intended behavior of this API, not hidden execution.

  5. info whitespace-anomaly pygments/lexers/_luau_builtins.py:2:1

    file mixes tab and space indentation (46 tab-indented and 5 space-indented lines) (raw pass, confidence 0.40)

    """
        pygments.lexers._luau_builtins
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  6. info warn → info by review dynamic-execution pygments/lexers/_mysql_builtins.py:1369:14

    `open(__file__)` — file reads its own source, common pattern for extracting payloads hidden in comments (ast pass, confidence 0.85)

    
            with open(__file__, encoding="utf-8") as f:
                data = f.read()
    likely benign LLM, confidence 0.90 Reads the module's own source as part of a data-regeneration helper.
    Reasoning

    This is a generated builtins data module that reads its own source to regenerate or inspect its keyword tables. Reading __file__ in a maintenance or regeneration helper does not extract hidden payloads. It is not executed on import.

  7. info warn → info by review dynamic-execution pygments/lexers/_mysql_builtins.py:1381:14

    `open(__file__)` — file reads its own source, common pattern for extracting payloads hidden in comments (ast pass, confidence 0.85)

    
            with open(__file__, 'w', encoding='utf-8', newline='\n') as f:
                f.write(data)
    likely benign LLM, confidence 0.90 Rewrites the module's own source with regenerated builtins data.
    Reasoning

    The same generated module writes its own file back with regenerated keyword data. This matches a code-generation script that rewrites a data table. There is no network, exec, or hidden payload involved.

  8. info warn → info by review dynamic-execution pygments/lexers/_scilab_builtins.py:3082:10

    `open(__file__)` — file reads its own source, common pattern for extracting payloads hidden in comments (ast pass, confidence 0.85)

    
        with open(__file__, encoding='utf-8') as f:
            content = f.read()
    likely benign LLM, confidence 0.90 Reads its own source in a generated-builtins maintenance path.
    Reasoning

    This is a generated Scilab builtins module that reads its own source during regeneration. The read is for maintenance tooling and does not run any extracted content.

  9. info warn → info by review dynamic-execution pygments/lexers/_scilab_builtins.py:3088:10

    `open(__file__)` — file reads its own source, common pattern for extracting payloads hidden in comments (ast pass, confidence 0.85)

    
        with open(__file__, 'w', encoding='utf-8') as f:
            f.write(header)
            f.write('# Autogenerated\n\n')
    likely benign LLM, confidence 0.90 Rewrites its own file with an autogenerated header during generation.
    Reasoning

    The snippet writes a header and an autogenerated marker back to the same file, consistent with a code generator rewriting its output. Nothing is executed and no payload is decoded.

  10. info unicode-mixed-script pygments/lexers/apl.py:62:25

    identifier `zΔ` mixes scripts: Latin + Greek (in string/comment) (raw pass, confidence 0.40)

                # following IBM APL2 standard
                (r'⎕[A-Za-zΔ∆⍙][A-Za-zΔ∆⍙_¯0-9]*', Name.Function),
                #
                # Labels
  11. info unicode-mixed-script pygments/lexers/apl.py:62:41

    identifier `zΔ` mixes scripts: Latin + Greek (in string/comment) (raw pass, confidence 0.40)

                # following IBM APL2 standard
                (r'⎕[A-Za-zΔ∆⍙][A-Za-zΔ∆⍙_¯0-9]*', Name.Function),
                #
                # Labels
  12. info unicode-mixed-script pygments/lexers/apl.py:67:24

    identifier `zΔ` mixes scripts: Latin + Greek (in string/comment) (raw pass, confidence 0.40)

                # following IBM APL2 standard
                # (r'[A-Za-zΔ∆⍙][A-Za-zΔ∆⍙_¯0-9]*:', Name.Label),
                #
                # Variables
  13. info unicode-mixed-script pygments/lexers/apl.py:67:40

    identifier `zΔ` mixes scripts: Latin + Greek (in string/comment) (raw pass, confidence 0.40)

                # following IBM APL2 standard
                # (r'[A-Za-zΔ∆⍙][A-Za-zΔ∆⍙_¯0-9]*:', Name.Label),
                #
                # Variables
  14. info unicode-mixed-script pygments/lexers/apl.py:72:22

    identifier `zΔ` mixes scripts: Latin + Greek (in string/comment) (raw pass, confidence 0.40)

                # following IBM APL2 standard (with a leading _ ok for GNU APL and Dyalog)
                (r'[A-Za-zΔ∆⍙_][A-Za-zΔ∆⍙_¯0-9]*', Name.Variable),     
                #
                # Numbers
  15. info unicode-mixed-script pygments/lexers/apl.py:72:39

    identifier `zΔ` mixes scripts: Latin + Greek (in string/comment) (raw pass, confidence 0.40)

                # following IBM APL2 standard (with a leading _ ok for GNU APL and Dyalog)
                (r'[A-Za-zΔ∆⍙_][A-Za-zΔ∆⍙_¯0-9]*', Name.Variable),     
                #
                # Numbers
  16. info unicode-homoglyph pygments/lexers/lean.py:32:29

    identifier `Zα` contains homoglyph candidates: α (03B1)→a (in string/comment) (raw pass, confidence 0.35)

        _name_segment = (
            "(?![λΠΣ])[_a-zA-Zα-ωΑ-Ωϊ-ϻἀ-῾℀-⅏𝒜-𝖟]"
            "(?:(?![λΠΣ])[_a-zA-Zα-ωΑ-Ωϊ-ϻἀ-῾℀-⅏𝒜-𝖟0-9'ⁿ-₉ₐ-ₜᵢ-ᵪ])*")
        _name = _name_segment + r"(\." + _name_segment + r")*"
  17. info unicode-mixed-script pygments/lexers/lean.py:32:29

    identifier `Zα` mixes scripts: Latin + Greek (in string/comment) (raw pass, confidence 0.40)

        _name_segment = (
            "(?![λΠΣ])[_a-zA-Zα-ωΑ-Ωϊ-ϻἀ-῾℀-⅏𝒜-𝖟]"
            "(?:(?![λΠΣ])[_a-zA-Zα-ωΑ-Ωϊ-ϻἀ-῾℀-⅏𝒜-𝖟0-9'ⁿ-₉ₐ-ₜᵢ-ᵪ])*")
        _name = _name_segment + r"(\." + _name_segment + r")*"
  18. info unicode-homoglyph pygments/lexers/lean.py:33:32

    identifier `Zα` contains homoglyph candidates: α (03B1)→a (in string/comment) (raw pass, confidence 0.35)

            "(?![λΠΣ])[_a-zA-Zα-ωΑ-Ωϊ-ϻἀ-῾℀-⅏𝒜-𝖟]"
            "(?:(?![λΠΣ])[_a-zA-Zα-ωΑ-Ωϊ-ϻἀ-῾℀-⅏𝒜-𝖟0-9'ⁿ-₉ₐ-ₜᵢ-ᵪ])*")
        _name = _name_segment + r"(\." + _name_segment + r")*"
  19. info unicode-mixed-script pygments/lexers/lean.py:33:32

    identifier `Zα` mixes scripts: Latin + Greek (in string/comment) (raw pass, confidence 0.40)

            "(?![λΠΣ])[_a-zA-Zα-ωΑ-Ωϊ-ϻἀ-῾℀-⅏𝒜-𝖟]"
            "(?:(?![λΠΣ])[_a-zA-Zα-ωΑ-Ωϊ-ϻἀ-῾℀-⅏𝒜-𝖟0-9'ⁿ-₉ₐ-ₜᵢ-ᵪ])*")
        _name = _name_segment + r"(\." + _name_segment + r")*"
  20. info unicode-homoglyph pygments/lexers/lean.py:149:29

    identifier `Zα` contains homoglyph candidates: α (03B1)→a (in string/comment) (raw pass, confidence 0.35)

        _name_segment = (
            "(?![λΠΣ])[_a-zA-Zα-ωΑ-Ωϊ-ϻἀ-῾℀-⅏𝒜-𝖟]"
            "(?:(?![λΠΣ])[_a-zA-Zα-ωΑ-Ωϊ-ϻἀ-῾℀-⅏𝒜-𝖟0-9'ⁿ-₉ₐ-ₜᵢ-ᵪ!?])*")
        _name = _name_segment + r"(\." + _name_segment + r")*"
  21. info unicode-mixed-script pygments/lexers/lean.py:149:29

    identifier `Zα` mixes scripts: Latin + Greek (in string/comment) (raw pass, confidence 0.40)

        _name_segment = (
            "(?![λΠΣ])[_a-zA-Zα-ωΑ-Ωϊ-ϻἀ-῾℀-⅏𝒜-𝖟]"
            "(?:(?![λΠΣ])[_a-zA-Zα-ωΑ-Ωϊ-ϻἀ-῾℀-⅏𝒜-𝖟0-9'ⁿ-₉ₐ-ₜᵢ-ᵪ!?])*")
        _name = _name_segment + r"(\." + _name_segment + r")*"
  22. info unicode-homoglyph pygments/lexers/lean.py:150:32

    identifier `Zα` contains homoglyph candidates: α (03B1)→a (in string/comment) (raw pass, confidence 0.35)

            "(?![λΠΣ])[_a-zA-Zα-ωΑ-Ωϊ-ϻἀ-῾℀-⅏𝒜-𝖟]"
            "(?:(?![λΠΣ])[_a-zA-Zα-ωΑ-Ωϊ-ϻἀ-῾℀-⅏𝒜-𝖟0-9'ⁿ-₉ₐ-ₜᵢ-ᵪ!?])*")
        _name = _name_segment + r"(\." + _name_segment + r")*"
  23. info unicode-mixed-script pygments/lexers/lean.py:150:32

    identifier `Zα` mixes scripts: Latin + Greek (in string/comment) (raw pass, confidence 0.40)

            "(?![λΠΣ])[_a-zA-Zα-ωΑ-Ωϊ-ϻἀ-῾℀-⅏𝒜-𝖟]"
            "(?:(?![λΠΣ])[_a-zA-Zα-ωΑ-Ωϊ-ϻἀ-῾℀-⅏𝒜-𝖟0-9'ⁿ-₉ₐ-ₜᵢ-ᵪ!?])*")
        _name = _name_segment + r"(\." + _name_segment + r")*"
  24. info warn → info by review decoder-decompress-payload pygments/lexers/special.py:87:28

    file imports decoder module(s) gzip, bz2 and calls `.decompress()` — embedded payload decompressed at runtime (ast pass, confidence 0.75)

                        import gzip
                        text = gzip.decompress(text)
                    elif self.compress == 'bz2':
                        import bz2
    likely benign LLM, confidence 0.88 Optional gzip/bz2 decompression of token-stream input in the raw-token lexer.
    Reasoning

    This is the Raw/Tokens lexer's optional decompression of its own compressed token-stream input when the compress option is set, a test and serialization feature. The bytes come from the caller's own input, not from a hidden embedded payload that is run.

  25. info unicode-homoglyph pygments/lexers/testing.py:31:374

    identifier `То` contains homoglyph candidates: Т (0422)→T, о (043E)→o (in string/comment) (raw pass, confidence 0.35)

    זי |אז |אבל |Якщо |Унда |То |Припустимо, що |Припустимо |Онда |Но |Нехай |Лекин |Когато |Када |Кад |К тому же |И |Задато |Задати |Задате |Если |Допустим |Дадено |Ва |Бирок |Аммо |Али |Але |А
  26. info unicode-homoglyph pygments/lexers/testing.py:31:440

    identifier `Но` contains homoglyph candidates: Н (041D)→H, о (043E)→o (in string/comment) (raw pass, confidence 0.35)

    , що |Припустимо |Онда |Но |Нехай |Лекин |Когато |Када |Кад |К тому же |И |Задато |Задати |Задате |Если |Допустим |Дадено |Ва |Бирок |Аммо |Али |Але |Агар |А |І |Și |És |Zatati |Zakładając |Zadato |Zadate |Za
  27. info unicode-homoglyph pygments/lexers/testing.py:31:502

    identifier `К` contains homoglyph candidates: К (041A)→K (in string/comment) (raw pass, confidence 0.35)

    кин |Когато |Када |Кад |К тому же |И |Задато |Задати |Задате |Если |Допустим |Дадено |Ва |Бирок |Аммо |Али |Але |Агар |А |І |Și |És |Zatati |Zakładając |Zadato |Zadate |Zadano |Zadani |Zadan |Youse know when youse got |Youse know lik
  28. info unicode-homoglyph pygments/lexers/testing.py:31:608

    identifier `Ва` contains homoglyph candidates: В (0412)→B, а (0430)→a (in string/comment) (raw pass, confidence 0.35)

    сли |Допустим |Дадено |Ва |Бирок |Аммо |Али |Але |Агар |А |І |Și |És |Zatati |Zakładając |Zadato |Zadate |Zadano |Zadani |Zadan |Youse know when youse got |Youse know like when |Yna |Ya know how |Ya gotta |Y |Wun |Wtedy |When y\'all |When |Wenn |WEN |Và |Ve |Und |Un |Thì |T
  29. info unicode-homoglyph pygments/lexers/testing.py:31:662

    identifier `А` contains homoglyph candidates: А (0410)→A (in string/comment) (raw pass, confidence 0.35)

    к |Аммо |Али |Але |Агар |А |І |Și |És |Zatati |Zakładając |Zadato |Zadate |Zadano |Zadani |Zadan |Youse know when youse got |Youse know like when |Yna |Ya know how |Ya gotta |Y |Wun |Wtedy |When y\'all |When |Wenn |WEN |Và |Ve |Und |Un |Thì |Then y\'all |Then |Tapi |Tak |Tada |Tad |Så |Stel |Soi
  30. info critical → info by review dynamic-import pygments/sphinxext.py:161:19

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

                module = data[0]
                mod = __import__(module, None, None, [classname])
                self.filenames.add(mod.__file__)
                cls = getattr(mod, classname)
    likely benign LLM, confidence 0.92 Dynamic import of a lexer class named in a Sphinx documentation directive.
    Reasoning

    This is the Sphinx extension's lookup of a lexer class by a module name taken from the project's own documentation directive. It is a developer-configured import in a documentation build step, not attacker-controlled input at runtime.

  31. info critical → info by review dynamic-import pygments/sphinxext.py:221:19

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

                module = data[0]
                mod = __import__(module, None, None, [classname])
                self.filenames.add(mod.__file__)
                cls = getattr(mod, classname)
    likely benign LLM, confidence 0.92 Dynamic import of a documented lexer class during Sphinx builds.
    Reasoning

    This is the same Sphinx extension lookup as the earlier finding, resolving a documented lexer class through __import__. It runs only during documentation builds on the developer's own config.

  32. info critical → info by review dynamic-import pygments/styles/__init__.py:45:15

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

        try:
            mod = __import__(mod, None, None, [cls])
        except ImportError:
            raise ClassNotFound(f"Could not find style module {mod!r}" +
    likely benign LLM, confidence 0.93 Dynamic import of a built-in style module with ClassNotFound fallback.
    Reasoning

    This is the standard style loader that imports a built-in style module by name. It raises ClassNotFound on ImportError, which is ordinary plugin-lookup behavior with an internal mapping.

  33. info high-complexity pygments/unistring.py:60:7

    compression ratio 0.629 is >2σ above file mean 0.384 (raw pass, confidence 0.50)

    
    Sc = '$\xa2-\xa5\u058f\u060b\u07fe-\u07ff\u09f2-\u09f3\u09fb\u0af1\u0bf9\u0e3f\u17db\u20a0-\u20bf\ua838\ufdfc\ufe69\uff04\uffe0-\uffe1\uffe5-\uffe6\U0001ecb0'
  34. info warn → info by review dynamic-attribute pygments/unistring.py:83:20

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

    def combine(*args):
        return ''.join(globals()[cat] for cat in args)
    likely benign LLM, confidence 0.92 Looks up Unicode range constants by name via globals() in a helper.
    Reasoning

    combine() looks up Unicode category range constants by name in the module's globals, drawing from a fixed set of internal string constants. This is a common Python idiom for a data-table helper and has no hidden behavior.

  35. info warn → info by review dynamic-attribute pygments/unistring.py:90:20

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

            newcats.remove(arg)
        return ''.join(globals()[cat] for cat in newcats)
    likely benign LLM, confidence 0.92 Second globals() constant lookup in the Unicode category combiner.
    Reasoning

    The same globals() lookup of internal Unicode category constants, in the helper that removes categories before combining. It is a data-construction idiom with no external input.

  36. info warn → info by review dynamic-execution pygments/unistring.py:115:10

    `open(__file__)` — file reads its own source, common pattern for extracting payloads hidden in comments (ast pass, confidence 0.85)

    
        with open(__file__, encoding='utf-8') as fp:
            content = fp.read()
    likely benign LLM, confidence 0.92 Reads its own source as part of a Unicode table generator.
    Reasoning

    This module reads its own source to build Unicode tables during generation. It is a generator or maintenance helper for a static data module, and nothing from the read is executed.

  37. info warn → info by review dynamic-execution pygments/unistring.py:139:10

    `open(__file__)` — file reads its own source, common pattern for extracting payloads hidden in comments (ast pass, confidence 0.85)

    
        with open(__file__, 'w', encoding='utf-8') as fp:
            fp.write(header)
    likely benign LLM, confidence 0.90 A pygments module writes a generated header to its own file path, consistent with a self-regenerating unicode data module.
    Reasoning

    The snippet opens __file__ in write mode and writes a header, which is a code generator regenerating the module's own unicode tables (pygments' unistring.py is produced by a script that rewrites itself). It does not read its own source for payload extraction, and it contains no exec, decoding, network or process calls. The ast rule fired on the open(__file__) call, which is a false positive here.

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 pygments-2.21.0-py3-none-any.whl https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl
printf '%s  %s\n' 2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9 pygments-2.21.0-py3-none-any.whl | shasum -a 256 -c -
unzip -q pygments-2.21.0-py3-none-any.whl -d pkg
disclude scan --untrusted --severity info pkg