Top/Articles/Dompdf: 6 Flaws Including CVE-2026-59941, PHP PDF Apps Need 3.1.6
dompdf-cve-cover-en

Dompdf: 6 Flaws Including CVE-2026-59941, PHP PDF Apps Need 3.1.6

Dompdf, the PHP library behind countless invoice and report PDFs, disclosed six flaws. A 58-byte image can exhaust server memory. The fix is 3.1.6.

NewsPublished July 29, 2026 Updated today
Table of contents
Key takeaways

Dompdf, the PHP library behind countless invoice and report PDFs, disclosed six flaws. A 58-byte image can exhaust server memory. The fix is 3.1.6.

Dompdf, the PHP library that turns HTML into PDF, has disclosed six vulnerabilities at once. They landed in the US National Vulnerability Database (NVD) between 20:17 and 21:17 UTC on July 28, 2026.

Everything up to and including version 3.1.5 is affected. The fix, Dompdf 3.1.6, shipped on July 20 β€” eight days before the CVE numbers went public. In other words, the patch already exists.

Dompdf is usually present without anyone remembering they installed it. Start there. Packagist, the PHP package registry, counts 193.8 million total downloads and 6.67 million in the past month. If you have ever built something in PHP that emits invoices, quotes, delivery notes or reports as PDF, this library was almost certainly on the shortlist. It also arrives bundled inside WordPress plugins. So the first section below is about how to find out whether you have it at all.

The six vulnerabilities at a glance

All six were published on July 22 as GitHub Security Advisories (GHSA) by the project itself, and reached NVD as CVE records six days later. As of July 29, 2026, NVD's own analysts have not assigned severity scores to any of them β€” all six sit in the "Received" state, awaiting analysis. The numbers in the table below are the project's own CVSS 4.0 self-assessments published through GHSA.

CVEIssueCVSS 4.0
(GHSA self-assessment)
Class (CWE)What happens
CVE-2026-55554Allowed-directory boundary
check can be bypassed
2.3 (Low)CWE-20 / CWE-22Files outside the allowed folder get read
CVE-2026-55555Font declaration used to
guess whether a file exists
2.3 (Low)CWE-203Attacker enumerates which files exist
CVE-2026-56722File path embedded in an
SVG image gets read
6.3 (Medium)CWE-20 / CWE-22Image files read in the default config
CVE-2026-59941BMP header dimensions
trusted without bounds
6.3 (Medium)CWE-400A 58-byte image eats hundreds of MB
CVE-2026-59942One oversized image
crashes the PHP process
6.3 (Medium)CWE-400 / CWE-770PDF generation stops, site stops responding
CVE-2026-59943Image references inside SVG
leak file and folder existence
6.3 (Medium)CWE-209Server layout can be mapped out

Two inconsistencies surfaced while building that table. The first is severity labelling. The project's 3.1.6 release notes mark all six as "Moderate", but the machine-readable severity from GitHub's advisory database rates CVE-2026-55554 and CVE-2026-55555 as "Low" β€” 2.3, against 6.3 for the other four. The second is version numbering, and it gets its own section further down.

What the crawlers hammering your forms are after

Nobody state-sponsored is needed here. The realistic user of these six is an automated crawler that hammers contact forms and quote-request forms and watches only for differences in the response. Pages with a "download PDF" button, screens that echo submitted input back as a confirmation PDF β€” those are the entry points being swept for, mechanically. Low skill required, no login required, so there is no need to pick a target.

Once a quote form that hands back a PDF turns up, the follow-up splits two ways. First, crafted image and font declarations are slipped into the text that becomes a PDF, and the server's files are identified one by one. Pinning down where config files or keys live is groundwork for the next attack. Second, a few dozen bytes of image data are sent in to exhaust the memory of the PDF process and take the whole site out of service.

"The order confirmation PDF never arrives." "The quote won't download." That is how it reaches the people using the service. For the team running it, PHP processes fall over and the site grinds first; in the worse case, the location and existence of server-side configuration files becomes known to an outsider. None of these six lets anyone take over the server, but they are perfectly usable as the reconnaissance step that precedes that. Which is why the check described next is the first move.

How to find out whether your system has Dompdf in it

Dompdf tends to arrive as somebody else's dependency rather than a direct choice. Packagist lists 741 packages that require it directly, and the repository has 11,159 stars. "I remember adding PDF export, I do not remember which library does it" is a completely normal state to be in. The fastest order of checks is below.

1. If Composer is available, start with one line

Run this at the project root, where composer.json lives.

composer show dompdf/dompdf

If it is installed, the version and description are printed. If not, you get a "not found" result.

To find out who brought it in, use composer why dompdf/dompdf (an alias of composer depends). It lists every package that requires Dompdf, which makes the path obvious: "it came through laravel-dompdf", "the reporting plugin pulled it in".

2. Let a tool do the version matching

Composer 2.4 and later ships a command that cross-checks installed packages against the advisory database.

composer audit

All six are already registered in Packagist's advisory database as "anything below 3.1.6 is affected", so a 3.1.5-or-older install produces six lines right here.

We covered how this kind of automated dependency matching works, and where it falls short, in our OSS supply chain scanner write-up. When a flaw lands in a component nobody remembers installing, that mechanism is the only thing that catches it.

3. Checking on servers without Composer

Plenty of production hosts have no Composer installed. In that case look at the files directly. Dompdf ships a plain-text file containing its version number, which makes this the most reliable check of all.

cat vendor/dompdf/dompdf/VERSION

grep -A2 '"name": "dompdf/dompdf"' composer.lock

The first prints the version verbatim, e.g. 3.1.5. The second reads the pinned version recorded in composer.lock.

4. On WordPress the path is unpredictable, so search for it

WordPress plugins often relocate and rename bundled libraries to avoid clashing with other plugins, so looking under vendor/dompdf/ finds nothing. Search the whole plugin tree instead.

find wp-content -type d -name dompdf

find wp-content -name VERSION -path '*dompdf*' -exec cat {} +

Line one locates every bundled copy, line two prints all their versions in one go.

One concrete example. "PDF Invoices & Packing Slips for WooCommerce", with 300,000 active installs on the WordPress plugin directory, bundles Dompdf. We unpacked the current 5.15.2 release ourselves: the bundled copy sits at vendor/strauss/dompdf/dompdf/ and its VERSION file reads 3.1.5. The plugin shipped on July 13 and Dompdf 3.1.6 on July 20, so the sequence is exactly what you would expect. It also means this is a setup where you have to wait for the plugin author. For how WordPress core updates factor in, see our WordPress core vulnerability roundup.

The single condition that decides whether you are exposed

Read the six descriptions side by side and they all rest on the same premise: can an attacker control the HTML that gets converted? The NVD text says as much in each case β€” "an attacker who controls the HTML input", "if a malicious actor can supply unrestricted content for rendering". Without that, none of them fires.

That line deserves to be drawn calmly. If your reports are a fixed server-side template with escaped database values dropped into it, there is very little here for anyone to attack. If, on the other hand, you turn contact-form submissions straight into PDFs, let users specify formatting, or expose an API that converts externally supplied HTML, that is the door.

Dompdf versionSource of the HTMLExposure to these sixPriority
3.1.6 or laterAnyNot affectedNo action
(review settings only)
3.1.5 or earlierIncludes raw
user input
All six can fireHighest
(update now)
3.1.5 or earlierHTML or URLs received
from outside
All six can fireHighest
(update now)
3.1.5 or earlierFixed template plus
escaped values only
Premise not metNormal
(next routine update)
3.1.5 or earlierInternal screens only
admins can reach
Hard to reach externallyNormal
(next routine update)
2.x or earlierAnyThese six plus older
critical flaws
Highest
(move to 3.x)

A note on that last row. These fixes exist only in 3.1.6; there is no backport to the 2.x line. Dompdf 2.x still carries far heavier problems than anything in this batch β€” CVE-2022-28368 (arbitrary code execution, 9.8) from 2022, and CVE-2023-23924 and CVE-2023-24813 (both 10.0) from 2023. If you are on 2.x, that needs handling regardless of today's news.

One more distinction affects reachability. CVE-2026-55554 and CVE-2026-55555 carry "passive user interaction required" (UI:P) in their CVSS vectors: the attacker plants the crafted HTML and waits for somebody to run a conversion on it. The other four need no user action at all (UI:N). That difference is what splits the scores between 2.3 and 6.3. "No authentication needed" does not mean the conditions are uniform.

Reading the six as two groups

Six items in a row do not stick. Split them into four that leak and two that knock you over β€” information disclosure on one side, loss of service on the other.

CVE-2026-55554: the allowed-directory check was a plain string prefix match

Dompdf has a restriction (the chroot setting) meaning "only read files under this folder". Per NVD, the method that enforces it, validateLocalUri(), normalised paths with realpath() and then used strpos() to check only whether the allowed path was a prefix of the target.

The catch is that normalisation strips the trailing separator. With the allowed folder set to /var/www, that string is also a prefix of /var/www2, /var/www-admin and /var/www_backup. A backup directory sitting next door becomes readable. The fix commit re-appends the separator before comparing, and only accepts an exact match or a prefix match that includes the separator. Reported by vxhex and snoopysecurity.

CVE-2026-56722: SVG is processed twice, and the second pass has no guard

Of the six, this is the closest thing to an actual file read. According to NVD, Dompdf processes an SVG twice, and the second pass does not enforce the same protections as the first. At render time the SVG is handed to a separate library, php-svg-lib, with external references forced on. That library knows nothing about Dompdf's chroot directory, blocks only the phar:// scheme, and then reads the referenced file with no path or protocol validation. The result, as written up, is that an external unauthenticated attacker can read arbitrary image files from the server in the default configuration.

This shape is familiar. GHSA-97m3-52wr-xvv2, published in February 2024, had the same structure β€” Dompdf using a value returned by php-svg-lib without validating it β€” and scored 10.0. Here the impact stops at reading image files, hence 6.3, but the same boundary keeps breaking in the same place. The fix is two commits: resolving local references from inside data-URI SVG documents correctly and rejecting SVGs whose file references do not resolve. This is also the only one of the six with no reporter credited on GHSA.

CVE-2026-55555: the font declaration answers "does this file exist?"

Point CSS @font-face (the rule that loads an external font) at a local file over file://, repeatedly, and Dompdf behaves differently depending on whether the file exists. An existing file is processed over and over until it triggers an "Allowed memory size exhausted" crash; a missing one fails fast or is ignored and never reaches the limit. Use that observable discrepancy as an oracle and you can enumerate files on the server one at a time, regardless of the chroot restriction.

There are conditions. The attacker needs a request that permits large payloads and unsanitised HTML, plus a Dompdf memory limit low enough to be exhausted. With warnings enabled ($_dompdf_show_warnings) the overflow is easier to reach. Reported by g4nkd; fixed by validating stylesheet URLs during resolution.

CVE-2026-59943: image references inside an SVG leak what exists on disk

Same mechanism as CVE-2026-55555, different tool: SVG instead of fonts. Put an <image> element inside an SVG embedded as a data URI and point its href or xlink:href at another file. NVD notes that Dompdf behaves differently for a path that does not exist (their example is file:///DOESNOTEXIST) than for a file or directory that does. The CWE class is CWE-209, error messages containing sensitive information. Even without reading contents, knowing "that config file is there" and "that directory exists" sharpens the next attempt. Reported by w4tchd0ge.

CVE-2026-59941: a 58-byte BMP eats the memory

For sheer concreteness, this one stands out. When Dompdf receives a BMP image it converts it into a PDF-compatible form. Per NVD, it did so based only on the dimensions declared in the header, never bounding width Γ— height before conversion.

The consequence: a 58-byte BMP whose header claims 6000Γ—6000 pixels is accepted. PHP's image function imagecreatetruecolor($width, $height) then goes and allocates the full declared canvas. Fifty-eight bytes of input turn into hundreds of megabytes of allocation.

The reporter, riodrwn, published measurements. A single 169-byte request drove a Dompdf render to roughly 412 MB peak RSS and about 4.8 seconds of CPU/wall time, against roughly 34 MB for an identically sized benign request β€” about 12Γ— memory amplification per request. And because the crafted BMP can be inlined as data:image/bmp;base64,… directly in the HTML, there is no upload and no remote fetch. It all fits in one HTTP request. That "tiny input, enormous work" shape is the same species as the small-request denial of service found in Apache HTTP/2.

CVE-2026-59942: one image is enough to crash the PHP process

Here an HTML document containing a single massive image (their example is 30,000Γ—30,000 pixels) crashes the PHP process. The awkward part is that Dompdf does implement image dimension checks, and they can be bypassed. NVD's description says a high-entropy image such as random noise, Base64-encoded and wrapped in specific CSS containers, slips past the validation.

The cause is ordering. Dimension validation happens early, but the allocation done later β€” computing the object's bounding box and the internal buffers during rendering β€” does not strictly cap cumulative CPU time or memory for a single object that already passed the initial check. An unauthenticated remote attacker submitting a crafted HTML string can take the web server fully out of service. Reported by far00t01; classified as both CWE-400 and CWE-770.

Both of these run through PHP's image processing (the GD extension). Dompdf's composer.json lists GD only as a suggestion, "needed to process images", but any environment producing PDFs with images in them will have it.

The fix is 3.1.6 β€” but watch out for "3.15" and "3.16" in the advisories

Version numbering is where these six are most likely to be read wrong. All six NVD descriptions say "versions 3.15 and prior" and "fixed in version 3.16". The machine-readable fields and the actual artifacts say something else.

SourceAffected range as writtenFixed version as written
NVD / GHSA prose3.15 and prior3.16
GHSA machine-readable data< 3.1.63.1.6
Packagist advisory database< 3.1.63.1.6
GitHub tags and releasesv3.1.5
(March 3, 2026)
v3.1.6
(July 20, 2026)
Bundled VERSION file3.1.53.1.6

So "3.15" means 3.1.5 and "3.16" means 3.1.6. Dompdf has never had a 3.15 or a 3.16. The tag list shows v3.1.6 as the newest, preceded by v3.1.5. If you are on 3.1.4 and read "3.15 and prior" as "I am below that, so I am fine", you get the answer backwards. Always decide on "is this 3.1.6 or later?"

How you upgrade depends on your dependency graph, but barryvdh/laravel-dompdf (103.8 million downloads), the usual Laravel wrapper, only requires dompdf/dompdf: ^3.1. So composer update dompdf/dompdf gets you to 3.1.6 without touching the wrapper.

The new size limit in 3.1.6, and the setting that disables it

Looking at what actually changed, the two denial-of-service issues brought a new configuration option with them. That behaviour change is worth knowing before you upgrade, so here is what following the commits turned up.

The commit that adds byte-size limits for images introduces an option called imageByteSizeLimit. It caps the estimated in-memory size of an image and, above the cap, skips rendering and records a warning instead. No explicit default is documented: the Options constructor reads PHP's memory_limit and feeds it straight in.

Which creates a trap. The setter, setImageByteSizeLimit(), treats any value of zero or below as "-1", meaning no limit. PHP expresses "unlimited" memory as -1. So on a host with memory_limit = -1, upgrading to 3.1.6 leaves the image size cap inactive. PHP run from the command line often defaults to unlimited memory, so batch jobs that mass-produce reports are a realistic candidate. The safe move is to upgrade and then set an explicit value, e.g. setImageByteSizeLimit('64M').

There is a hazard in the other direction too. Because the default cap equals memory_limit, large images that used to render may now be skipped with only a warning after the upgrade. Once on 3.1.6, check that report templates with big photos or many logo images still come out correctly. The project's Securing Dompdf wiki page recommends never letting users control the configuration, sanitising input, and installing Dompdf outside the document root.

Exploitation status, and how Japan is handling it

Here is what the external indicators show right now.

βœ“ Confirmed facts

  • βœ“None of the six has an NVD-analyst severity score. All are in the "Received" state awaiting analysis (NVD, checked July 29, 2026)
  • βœ“None appears in CISA's Known Exploited Vulnerabilities catalog. Checked against catalog version 2026.07.27, 1,655 entries
  • βœ“No EPSS exploitation-probability score has been computed for any of them; FIRST's API returns zero results
  • βœ“Only CVE-2026-55555 carries a CISA SSVC assessment in NVD, recording exploitation as "poc", automatable as "no", technical impact as "partial"
  • βœ“Nothing is registered with Japan's JVN or JVN iPedia. A MyJVN API search for "dompdf" returns zero results (checked July 29, 2026). No JPCERT/CC alert and no IPA advisory could be found either
  • βœ“No Japanese-language coverage could be found. We also searched X for posts by the maintainers and the reporters, and found none referring to these six

? Still unknown

  • ?What severity NVD analysts will assign β€” it may move away from the project's self-assessment (2.3 for two, 6.3 for four)
  • ?Whether proof-of-concept code exists for the other five; only one has a CISA assessment attached
  • ?When each library and plugin that bundles Dompdf will pick up 3.1.6

The absence of Japanese registrations and Japanese articles also means there is almost nothing readable in Japanese about these six yet. For how we track KEV listings over time, see the CISA KEV dashboard.

On scale, honestly: this is a library with 193 million downloads, but all six require attacker-controlled HTML, and the worst outcome is a file read or a stopped service. Nothing here hands over the server. That said, for sites that do meet the condition β€” anywhere a user's typed text becomes a PDF β€” what just became available is an attack that needs no login, fits in one request, and works reliably. Compared with the unauthenticated remote code execution we covered in Woo PDF Invoice Builder, the weight is different, but the way you scope your exposure is identical.

Upgrade to 3.1.6, then check the new limit

Start by running composer show dompdf/dompdf and composer audit to settle whether Dompdf is present and, if so, whether it is below 3.1.6. On WordPress that is find wp-content -type d -name dompdf. If nothing turns up, you are done.

If it does turn up, work out where the text on those PDFs comes from. Escaped database values in a fixed template is not an emergency; bring it to 3.1.6 at the next routine update. If user input or externally supplied HTML gets mixed in, run composer update dompdf/dompdf today.

Where it arrived bundled inside a plugin and you cannot bump it yourself, you are waiting on the plugin author. In the meantime you can disable remote resource fetching, narrow the allowed read directory to what is genuinely needed, and put size and rate limits on the endpoints that produce PDFs. The two denial-of-service issues in particular are "hundreds of megabytes per request", so revisiting concurrency caps and timeouts alone helps.

After moving to 3.1.6, verify once that reports containing large images still render as before β€” the imageByteSizeLimit described above becomes active by default, so behaviour can change. Treat all of that as one pass and write it down as a dependency inventory; it shortens the response next time. We work through the same reasoning in our Apache Tomcat vulnerability roundup.

On the broader question of why PDF-generating libraries can read server files at all, GMO Cybersecurity by Ierae has a Japanese-language write-up on server-side PDF generation flaws that is worth the machine translation. This class of hole keeps reappearing in the same places, well beyond these six.

References

avatar-m-1

Makoto Horikawa

Backend Engineer / AWS / Django