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



GHSA-4XC4-762W-M6CG

pypdf CVE-2026-22690: Killing Servers with Kindness

Alon Barad
Alon Barad
Software Engineer

Feb 22, 2026·6 min read·6 visits

Executive Summary (TL;DR)

pypdf tries too hard to fix broken PDFs. If a file is missing the Root object but claims to have 2 billion objects in its Size trailer, pypdf will check every single one. This loops until the CPU burns out or the universe ends.

A resource consumption vulnerability in pypdf allows attackers to trigger a Denial of Service via malformed PDF trailers. By removing the '/Root' key and inflating the '/Size' parameter, the library enters an effectively infinite loop trying to 'repair' the file, consuming 100% CPU.

The Hook: When Features Become Bugs

We often praise software for being "robust" or "fault-tolerant." In the world of PDF parsing—a format that is essentially a dumpster fire of legacy specs and vendor-specific hacks—libraries have to be forgiving. If a PDF is slightly broken, users expect the library to fix it and show them the content. Enter pypdf, a pure-Python library that powers thousands of document processing pipelines.

But here is the catch: there is a fine line between being helpful and being gullible. CVE-2026-22690 is a classic example of the latter. It is not a memory corruption bug; it is a logic flaw born from kindness. When pypdf encounters a specifically malformed PDF, it doesn't throw an error. Instead, it rolls up its sleeves and attempts a "recovery" operation that an attacker can trick into taking effectively forever.

This isn't about stealing data; it's about freezing the gears of any application that processes untrusted PDFs. Think invoice parsers, resume scanners, or automated archiving bots. One 1KB file can lock up a worker thread indefinitely.

The Flaw: Trusting the /Size

To understand the bug, you need to know a tiny bit about PDF structure. A PDF has a "Trailer" dictionary that tells the parser where to start. The most important key in the trailer is /Root, which points to the Document Catalog (the root of the object tree). The trailer also usually contains a /Size key, indicating the total number of objects in the file.

Here is the logic flaw in pypdf versions prior to 6.6.0: If the /Root key is missing (which makes the PDF technically invalid), the library assumes the file is just slightly corrupted. It activates a "recovery mode" to hunt for the Catalog manually.

How does it know where to look? It asks the /Size key. If the file says, "Hey, I have 100 objects," pypdf iterates through indices 0 to 100, resolving each object to see if it looks like a Catalog. The problem is that /Size is just a number in the text file. An attacker can set /Size to 2,147,483,647 (INT_MAX), remove the /Root key, and provide a file with only 1 actual object. The library will then dutifully attempt to resolve 2 billion non-existent objects, burning CPU cycles on dictionary lookups and file seeking for hours.

The Code: The Loop of Doom

Let's look at the smoking gun in pypdf/_reader.py. This is the code that runs when strict=False (which is often the default or preferred mode for compatibility).

Vulnerable Code (< 6.6.0):

# Inside PdfReader.root_object
root = self.trailer.get("/Root")
if root is None:
    # Oh no, no Root! Let's find it.
    nb = self.trailer.get("/Size", 0)
    # The Loop of Doom:
    for i in range(nb): 
        # This triggers parsing logic for every theoretical ID
        obj = self.get_object(i + 1)
        if isinstance(obj, DictionaryObject) and obj.get("/Type") == "/Catalog":
            self._validated_root = obj
            break

See that range(nb)? That is the kill switch. The variable nb is taken directly from the attacker-controlled input. There was no cap, no timeout, and no sanity check.

The Fix (v6.6.0):

The maintainers introduced a sanity limit. Even if the file claims to have billions of objects, the recovery logic now gives up after a set number of attempts (default 10,000).

# The patched logic
limit = self.root_object_recovery_limit  # Default 10000
nb = self.trailer.get("/Size", 0)
 
# Bounded range prevents infinite loop
for i in range(min(nb, limit)):
    # ... logic ...

It is a simple fix: never trust the input to define the bounds of your loops.

The Exploit: Building the PDF Bomb

We don't need a complex fuzzer to trigger this. We can write this "exploit" by hand in a text editor. We need a valid PDF header, one dummy object so the parser doesn't crash immediately, and a malicious trailer.

Here is the recipe for disaster:

  1. Header: %PDF-1.7
  2. Body: A single useless object.
  3. Trailer: Omit /Root, set /Size to max integer.
# malicious_gen.py
exploit_pdf = (
    b"%PDF-1.7\n"                 # Header
    b"1 0 obj << >> endobj\n"     # Object 1 (Dummy)
    b"trailer << "
    b"  /Size 2147483647 "        # The Trap: 2 billion objects
    b">>\n"                       # Note: No /Root key!
    b"startxref\n0\n%%EOF"        # End of file
)
 
with open("dos.pdf", "wb") as f:
    f.write(exploit_pdf)

When a vulnerable pypdf instance opens this file and tries to access reader.pages or any property requiring the root, it hits the root_object() method. It sees root is missing. It reads /Size. It starts counting. If you monitor the process, you'll see one CPU core instantly pin to 100%. In a single-threaded Python web worker, this request will never return until the web server times it out.

The Impact: Why Denial of Service Matters

Security researchers often roll their eyes at DoS bugs because they don't provide a shell. But in the context of modern cloud architecture, this is a wallet-draining vulnerability.

Imagine a SaaS platform that allows users to upload PDFs for OCR or signing. These services often use Python backends (Django/Flask/FastAPI) wrapping pypdf. If an attacker uploads 10 of these 1KB files, they can permanently lock up 10 worker processes.

If the infrastructure creates new instances to handle load (autoscaling), the attacker just triggered a financial exploit—forcing the victim to pay for compute credits to process a loop that does nothing. Because this happens in user-space Python code, it might not trigger low-level segfault protections. It just sits there, burning electricity.

The Fix: Stopping the Bleeding

The remediation is straightforward, but it requires action. The patch was released in version 6.6.0.

Primary Fix: Update your requirements file immediately.

pip install pypdf>=6.6.0

Workaround (If you can't update): If you are stuck on legacy versions, you must instantiate the PdfReader with strict mode enabled. This disables the recovery logic entirely. If a PDF is broken, it will raise an exception instead of looping forever.

# strict=True disables the "best effort" recovery
reader = PdfReader(stream, strict=True)

However, be warned: strict=True is very strict. It will reject many benign-but-slightly-malformed PDFs that users generate from cheap export tools. The only real fix is the patch.

Official Patches

pypdfPR #3594: Add recovery limit for root object search

Fix Analysis (1)

Technical Appendix

CVSS Score
5.3/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
EPSS Probability
0.02%
Top 95% most exploited

Affected Systems

pypdf < 6.6.0Python applications processing untrusted PDFsDocument ingestion pipelines

Affected Versions Detail

Product
Affected Versions
Fixed Version
pypdf
py-pdf
< 6.6.06.6.0
AttributeDetail
CWECWE-400 (Uncontrolled Resource Consumption)
CVSS v3.15.3 (Medium)
Attack VectorNetwork (Context-dependent)
ImpactDenial of Service (High CPU/Hang)
EPSS Score0.00019 (Low Probability)
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 (CPU), thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.

Known Exploits & Detection

GitHub (pypdf tests)The official repository contains a test case demonstrating the large /Size parameter triggering the limit.

Vulnerability Timeline

Fix implemented in PR #3594
2026-01-07
pypdf v6.6.0 released
2026-01-09
CVE-2026-22690 Published
2026-01-10

References & Sources

  • [1]GHSA Advisory
  • [2]pypdf 6.6.0 Release Notes
Related Vulnerabilities
CVE-2026-22690

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

•7 days ago•CVE-2026-9354
6.9

CVE-2026-9354: Arbitrary Mass Mention Bypass in NousResearch hermes-agent Slack and Mattermost Adapters

A vulnerability in the Slack and Mattermost platform adapters for NousResearch hermes-agent permits an unauthenticated remote attacker to execute arbitrary mass mentions. By leveraging prompt injection, an attacker can bypass output sanitization logic and trigger workspace-wide notification exhaustion.

Alon Barad
Alon Barad
35 views•6 min read
•7 days ago•CVE-2026-9306
6.3

CVE-2026-9306: Unauthenticated Insecure Direct Object Reference (IDOR) in QuantumNous new-api Midjourney Relay

CVE-2026-9306 is a critical unauthenticated Insecure Direct Object Reference (IDOR) vulnerability located in the QuantumNous new-api application, affecting versions up to and including 0.12.1. The flaw is caused by improper middleware ordering combined with a lack of object-level authorization checks. This allows remote, unauthenticated attackers to retrieve sensitive Midjourney images belonging to other users by supplying a valid task identifier.

Amit Schendel
Amit Schendel
13 views•5 min read
•8 days ago•GHSA-GGXF-37HM-9WQF
6.5

GHSA-GGXF-37HM-9WQF: Session Leakage via Unsafe Challenge Path Parsing in instagrapi

The instagrapi library prior to version 2.6.9 contains an improper input validation vulnerability within its challenge handling mechanism. Maliciously crafted server responses can manipulate the client into forwarding session cookies and credentials to an external attacker-controlled domain.

Amit Schendel
Amit Schendel
21 views•6 min read
•8 days ago•GHSA-QQQM-5547-774X
9.1

GHSA-QQQM-5547-774X: Unauthenticated Path Traversal in FileBrowser Quantum PATCH Handler

GHSA-QQQM-5547-774X is a critical path traversal vulnerability in the FileBrowser Quantum application, specifically within the Go backend package. The vulnerability resides in the HTTP handler responsible for processing bulk file modifications via the public API. Unauthenticated attackers can exploit an order-of-operations flaw in the path sanitization logic to bypass intended directory restrictions. This allows adversaries to arbitrarily read, move, and overwrite files on the underlying filesystem by supplying specially crafted HTTP PATCH requests.

Alon Barad
Alon Barad
9 views•6 min read
•8 days ago•CVE-2026-8723
5.3

CVE-2026-8723: Synchronous Denial of Service in qs npm Package via TypeError

The qs query string parsing and serialization library for Node.js is vulnerable to a synchronous Denial of Service (DoS) attack. The vulnerability manifests as a process-terminating TypeError when processing arrays with null or undefined elements under specific configuration parameters.

Amit Schendel
Amit Schendel
37 views•7 min read
•8 days ago•GHSA-7M8F-HGJQ-8GC9
7.5

GHSA-7M8F-HGJQ-8GC9: Pre-Authentication Denial of Service via Insecure Deserialization Order in aiosend

The aiosend library prior to version 3.0.6 contains a pre-authentication Denial of Service (DoS) vulnerability in its webhook handling mechanism. The software processes and deserializes incoming JSON payloads before verifying the cryptographic signature, allowing unauthenticated attackers to exhaust server CPU and memory resources by sending large, complex payloads.

Amit Schendel
Amit Schendel
4 views•6 min read