Last updated: 26 October 2026. Sources are listed at the end.

A Pillow vulnerability tracked as CVE-2026-112900 lets an attacker inject options into the Ghostscript command that Pillow runs when it renders EPS (Encapsulated PostScript) images. The Pillow maintainers fixed it in Pillow 13.0.0, released on 16 October 2026. The Hacker News covered it on 23 October under a “critical flaw” headline, so many Indian teams are now seeing it in scanner reports.

Pillow is the standard image library for Python. It sits under Django and Flask upload forms, FastAPI services, thumbnail workers, CMS platforms and many machine learning pipelines. However, this particular bug needs a specific set of conditions. In this guide we explain what those conditions are, how to check your servers, and how to patch. We also cover two smaller EPS bugs that scanners flag alongside it.

The short answer

  • Upgrade to Pillow 13.0.0 with python3 -m pip install --upgrade "pillow>=13.0.0", then rebuild every container image and virtual environment.
  • Pillow 13.0.0 needs Python 3.11 or newer. On Python 3.10, pip silently stays on 12.3.0, which is still vulnerable.
  • If you do not need EPS files, remove Ghostscript (gs) from servers that process uploads, or set EpsImagePlugin.gs_binary = False.
  • Open untrusted images with an allowlist, for example Image.open(fp, formats=["PNG", "JPEG", "WEBP", "GIF"]).
  • Never keep the uploader’s original filename on disk. Store uploads under a random name instead.

What the Pillow vulnerability actually is

Pillow cannot draw PostScript by itself. So when your code loads an EPS image, Pillow calls the external Ghostscript program (gs) and passes it the path of the file. Before 13.0.0, Pillow passed that path exactly as it found it, after a -f switch.

That is the bug. Ghostscript reads any argument that begins with a hyphen as an option. In addition, an argument that begins with @ tells Ghostscript to read more arguments from a text file. As a result, a file named something like -sSomething.eps becomes a Ghostscript option instead of an input file.

According to the GitHub advisory (GHSA-w889-685w-c8fh), the reporter showed a single injected option working every time with Pillow’s default settings. The reporter also chained the @ trick to run the id command on Pillow 12.3.0 with Ghostscript 10.02.1. Importantly, the default -dSAFER flag did not stop it. That flag restricts what PostScript code may do; it does not control how Ghostscript parses its own command line.

The advisory makes one point very clear: the filename triggers the flaw, not the image content. A perfectly harmless EPS file can be dangerous if an attacker chooses its name. Therefore, the headline phrase “crafted image” is a little misleading.

What an attacker needs

Our reading of the advisory and the fixed code shows four conditions. All of them must be true:

  1. Ghostscript is installed and on the PATH of the process that runs Pillow.
  2. Your application saves the file under a name the attacker can influence, then opens it by that path.
  3. The file is an EPS image, and your code calls something that renders it, such as load(), thumbnail(), convert() or save().
  4. For full command execution, the attacker also needs to place a second text file (the @ argument file) where Ghostscript can read it.

Meanwhile, code that opens uploads from memory (for example io.BytesIO) takes a different branch. In that case Pillow copies the data to a temporary file with a safe random name, so the injection does not happen.

Severity: why you see both “Moderate” and “9.2”

Sources disagree on severity, and that is worth knowing before you escalate internally. The Pillow maintainers rate the advisory Moderate, and the 13.0.0 release notes list it under Medium. However, the CVE record carries a CVSS 4.0 score of 9.2 (Critical), assigned through GitHub as the CNA. The Hacker News also pointed out this gap. In practice, the score assumes conditions that many applications do not meet.

Timeline

Date (2026)Event
1 JulyPillow 12.3.0 released (fixes CVE-2026-59203)
16 OctoberPillow 13.0.0 released with the Ghostscript path fix
17 OctoberGitHub advisories for the EPS bugs published
19 OctoberCVE-2026-112900 published; NVD analysis still pending
23 OctoberThe Hacker News report; Ubuntu lists it as Medium priority

Exploitation status

No in-the-wild exploitation has been reported. The Hacker News states that none of the 13.0.0 flaws is known to be under attack. Also, we have not seen CVE-2026-112900 added to the CISA Known Exploited Vulnerabilities catalogue. The reporter’s proof of concept exists, but the advisory withholds the payload details, and we have not seen a public exploit.

The two related EPS bugs

CVE-2026-112899 (GHSA-vx6w-vv9f-6gvg) is a resource-consumption bug. If your code calls load(scale=N) on an untrusted EPS file, Ghostscript can render far beyond Pillow’s decompression bomb limit. The result is wasted CPU and large temporary files, but no memory corruption. The maintainers rate it Low. It affects Pillow 2.3.0 to 12.3.0, and 13.0.0 fixes it.

CVE-2026-59203 is an infinite loop in the EPS parser. A negative byte count in a %%BeginBinary line makes Image.open() spin forever, even without Ghostscript. It affects only Pillow 12.0.0 to 12.2.0, and the fix shipped earlier, in 12.3.0, not in 13.0.0.

Also note that 13.0.0 fixes dozens of other issues, including two maintainer-rated critical memory bugs and a FlashPix JPEG flaw (CVE-2026-112398). So the upgrade is worth doing even if you never touch EPS files.

Am I affected?

CVEWhat it isAffected PillowFixed in
CVE-2026-112900Ghostscript argument injection via filename2.6.0 to 12.3.013.0.0
CVE-2026-112899EPS load(scale) bypasses bomb limit2.3.0 to 12.3.013.0.0
CVE-2026-59203EPS %%BeginBinary infinite loop12.0.0 to 12.2.012.3.0

First, check the Pillow version in each environment your app uses. Run these inside the right virtual environment:

pip show pillow
python3 -c "import PIL; print(PIL.__version__)"
python3 --version

Next, find every copy on a server, because old virtual environments often linger:

find / -xdev -type d -iname "pillow-*.dist-info" 2>/dev/null

Then check distribution packages and Ghostscript:

dpkg -l python3-pil 2>/dev/null | grep '^ii'
rpm -q python3-pillow
command -v gs && gs --version

If gs is missing, the injection path cannot run on that host. Still, upgrade anyway, since 13.0.0 fixes many other bugs.

For projects, pip-audit reads your requirements file and lists known CVEs. For example:

pip install pip-audit
pip-audit -r requirements.txt

In our test, a file pinning pillow==12.3.0 returned CVE-2026-112900, CVE-2026-112899 and many others, each with fix version 13.0.0.

For Docker images, check inside the image rather than the host:

docker run --rm --entrypoint python3 your-image:tag -c "import PIL; print(PIL.__version__)"

Finally, look at how your code opens images. Search for Image.open( calls that receive a path built from an uploaded filename:

grep -rnE "Image\.open\(" --include="*.py" /path/to/your/app

How to fix it

Step 1: Upgrade Pillow

python3 -m pip install --upgrade "pillow>=13.0.0"

Then pin it in requirements.txt as pillow>=13.0.0, and rebuild your containers. Because Pillow 13 dropped Python 3.10, this command fails on Python 3.10. That failure is useful, since it tells you the runtime must move to 3.11 or later first.

Distribution packages lag behind. At the time of writing, Debian lists bookworm, trixie and forky as vulnerable, with only unstable (sid) fixed. Ubuntu marks every release as “needs evaluation”. So if you rely on python3-pil from apt, use the mitigations below until your distribution ships a fix.

Step 2: Rename uploads

This step removes the root condition even on old versions. Save each upload under a random name that you generate, for example:

import os, uuid
safe_path = os.path.join(UPLOAD_DIR, f"{uuid.uuid4().hex}.img")

Alternatively, read the upload into memory and pass io.BytesIO(data) to Pillow.

Step 3: Allow only the formats you need

Pillow detects formats by content, not by file extension. Therefore, a file called photo.jpg can still reach the EPS plugin. An allowlist stops that:

from PIL import Image, UnidentifiedImageError

ALLOWED = ["PNG", "JPEG", "WEBP", "GIF"]

try:
    with Image.open(upload_path, formats=ALLOWED) as im:
        im.load()
except UnidentifiedImageError:
    reject_upload()

We ran this against a sample PNG and a sample EPS on Pillow 13.0.0. The PNG opened normally, while the EPS raised cannot identify image file.

Step 4: Turn off Ghostscript or the EPS plugin

If you cannot change every Image.open() call, disable EPS rendering once at start-up:

from PIL import Image, EpsImagePlugin

EpsImagePlugin.gs_binary = False  # never call Ghostscript

Image.init()
Image.OPEN.pop("EPS", None)       # stop Pillow opening EPS at all
if "EPS" in Image.ID:
    Image.ID.remove("EPS")

The gs_binary = False setting comes from Pillow’s format documentation. The unregister lines use internal Pillow structures, so retest them after each upgrade. In our test, EPS files were rejected while PNG files still opened.

Also consider removing the ghostscript package from upload servers. Check first that nothing else on the host, such as a PDF tool, depends on it.

Step 5: Check magic bytes and sandbox the worker

As an extra layer, check the first bytes of each file before Pillow sees it. PNG starts with \x89PNG, JPEG with \xff\xd8\xff, GIF with GIF87a or GIF89a, and WebP with RIFF....WEBP. In addition, run image processing in a separate worker with no shell access, a read-only filesystem and no outbound network.

What about Wand and ImageMagick?

ImageMagick also hands PostScript and PDF to Ghostscript. If you use Wand or ImageMagick on uploads, set a restrictive policy.xml. The ImageMagick security policy page gives this allowlist example:

<policy domain="module" rights="none" pattern="*"/>
<policy domain="module" rights="read | write" pattern="{GIF,JPEG,PNG,WEBP}"/>

Then confirm the active policy with magick identify -list policy (taken from ImageMagick docs).

Was I compromised?

No indicators of compromise have been published for CVE-2026-112900, so we will not invent any. Instead, look for the conditions. Search upload folders for stored filenames that start with - or @:

find /path/to/uploads \( -name '-*' -o -name '@*' \) -ls

Also review process logs or audit records for gs processes started by your web user. A shell, curl or wget spawned under that user is a strong warning sign.

The India angle

We have not seen a CERT-In advisory for this bug at the time of writing. Even so, if you confirm a compromise, entities covered by the CERT-In Directions of April 2022 must report the incident within 6 hours. From May 2027, DPDP Rule 7 will also require a detailed report to the Data Protection Board within 72 hours when personal data is exposed.

Quick checklist

  • ☐ List every Pillow install: venvs, containers, apt packages
  • ☐ Upgrade to Pillow 13.0.0 and move off Python 3.10
  • ☐ Rebuild and redeploy container images
  • ☐ Store uploads under random names, never the original filename
  • ☐ Add formats=[...] to every Image.open() on untrusted input
  • ☐ Remove Ghostscript from upload servers or set gs_binary = False
  • ☐ Apply an ImageMagick policy if you use Wand
  • ☐ Add pip-audit to your CI pipeline

If your team runs Python web apps that accept uploads, our VAPT services test exactly these upload paths. For broader server clean-up, see our Linux server hardening audit. We also covered another Python supply-chain issue in the LiteLLM 1.107.0 compromise guide.

Sources

Want a second pair of eyes on your DevOps security? Our VAPT services cover CI/CD pipelines, self-hosted tools and how secrets are stored. For servers, see our Linux server hardening & security audit, or contact us.

Post author avatar

Bhaskar Soni

Bhaskar Soni is a Security Engineer and the founder of Youngster Company, based in Ahmedabad, Gujarat. He works hands-on with Linux server hardening, vulnerability assessment and penetration testing (VAPT), ISO 27001 compliance and OSINT investigations. He writes practical, tested guides for IT professionals and Indian businesses, and every article on this site is written or reviewed by him.

Leave a Reply