high · 7.5Aug 25, 2026

CVE-2026-9769: justhtml Uncontrolled Recursion DoS via Deeply Nested HTML

Shubham Kandhare
Security Engagement Manager, SecureLayer7

Passing roughly 1000 nested HTML tags to the justhtml parser crashes the Python process with an unhandled RecursionError, letting any attacker who can supply HTML input take down a server worker.

Packagejusthtml
Ecosystempip
Affected<= 1.9.1
CVE-2026-9769: justhtml Uncontrolled Recursion DoS via Deeply Nested HTML

The problem

In justhtml <= 1.9.1, every call to JustHTML() eventually reaches TreeBuilder.finish(), which unconditionally calls _populate_selectedcontent(). That function searches the DOM for <select> elements by calling _find_elements() and _find_element() recursively, with no depth limit.

Because every level of DOM nesting consumes one Python call frame, an attacker supplying ~1000 nested tags is enough to exceed CPython's default recursion limit of 1000. The resulting RecursionError is unhandled, so depending on the host application it can abort parsing, fail HTTP requests, or kill an entire worker process.

No authentication is required.

Proof of concept

A working proof-of-concept for this issue in justhtml, with the exact payload below.

python
from justhtml import JustHTML

# ~11 KB of input, triggers RecursionError on CPython default stack
html = "<div>" * 1000 + "x" + "</div>" * 1000
doc = JustHTML(html)  # raises RecursionError

The root cause (CWE-674) is that _find_elements() calls itself once per child node, so DOM depth maps directly to Python call-stack depth. There is no guard, no sys.setrecursionlimit bump, and no iterative fallback. CPython's default limit of 1000 frames is trivially reachable with ~1000 nested tags.

The v1.10.0 patch converts _find_elements(), _find_element(), clone_node(deep=True), _node_to_html(), and _to_markdown_walk() from recursive functions to iterative ones using an explicit stack (a plain Python list). This removes any dependency on the call stack and makes DOM depth irrelevant to safety.

The fix

Upgrade to justhtml >= 1.10.0. The fix replaces all recursive DOM-traversal functions with iterative stack-based equivalents, so arbitrarily deep documents no longer grow the Python call stack. No configuration change alone can mitigate the issue on affected versions; the only safe option is the upgrade.

Reported by kq5y.

References: [1][2][3][4]

Related research