CVE-2026-9769
Summary
justhtml through 1.9.1 allows denial of service via deeply nested HTML. During parsing, JustHTML.init() always reaches TreeBuilder.finish(), which unconditionally calls _populate_selectedcontent(). That function recursively traverses the DOM via _find_elements() / _find_element() without a depth bound, allowing attacker-controlled deeply nested input to trigger an unhandled RecursionError on CPython. Depending on the host application's exception handling, this can abort parsing, fail requests, or terminate a worker/process.
Details
TreeBuilder.finish() (treebuilder.py#L476) unconditionally calls _populate_selectedcontent(self.document) at line 494. _populate_selectedcontent() (treebuilder.py#L1243) calls _find_elements() (treebuilder.py#L1280) to recursively search the DOM tree for <select> elements:
def _find_elements(self, node: Any, name: str, result: list[Any]) -> None:
"""Recursively find all elements with given name."""
if node.name == name:
result.append(node)
if node.has_child_nodes():
for child in node.children:
self._find_elements(child, name, result) # recursive call
When the DOM tree depth exceeds CPython's default recursion limit (1000), this raises an unhandled RecursionError. The full call path is:
JustHTML(html) → tokenizer.run() → tree_builder.finish() → _populate_selectedcontent(document) → _find_elements(root, "select", selects) (recursive)
Deeply nested DOM trees can be produced by nesting <div> tags ~1000 levels deep. On CPython with the default recursion limit, approximately 11 KB of <div> nesting is sufficient to trigger the error. The exact depth threshold is environment-dependent (CPython version, recursion limit setting, call stack depth at invocation).
Additional recursive functions are affected on already-parsed deep trees:
- Node.clone_node(deep=True) (node.py#L523) — called during sanitization
- _node_to_html() (serialize.py#L580) — used by to_html(pretty=True)
- _to_markdown_walk() (node.py#L817) — used by to_markdown()
⚡ Watch CVE-2026-9769
Get an email if CVE-2026-9769 is added to CISA KEV, gains public exploit code, or a new advisory cites it — max one per day, one-click unsubscribe.
Exploitation outlook
- Low exploitation risk0.28% 30-day exploitation probability — currently an unlikely target, but scores change as exploit code circulates. Riskier than 20% of all EPSS-scored CVEs.
Advisory coverage (2)
External references
Embed the live status
— this badge updates automatically when the KEV or exploit status changes. How to embed it →
[](https://www.csirts.com/cve/CVE-2026-9769)