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 setEpsImagePlugin.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:
- Ghostscript is installed and on the PATH of the process that runs Pillow.
- Your application saves the file under a name the attacker can influence, then opens it by that path.
- The file is an EPS image, and your code calls something that renders it, such as
load(),thumbnail(),convert()orsave(). - 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 July | Pillow 12.3.0 released (fixes CVE-2026-59203) |
| 16 October | Pillow 13.0.0 released with the Ghostscript path fix |
| 17 October | GitHub advisories for the EPS bugs published |
| 19 October | CVE-2026-112900 published; NVD analysis still pending |
| 23 October | The 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?
| CVE | What it is | Affected Pillow | Fixed in |
|---|---|---|---|
| CVE-2026-112900 | Ghostscript argument injection via filename | 2.6.0 to 12.3.0 | 13.0.0 |
| CVE-2026-112899 | EPS load(scale) bypasses bomb limit | 2.3.0 to 12.3.0 | 13.0.0 |
| CVE-2026-59203 | EPS %%BeginBinary infinite loop | 12.0.0 to 12.2.0 | 12.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 --versionNext, find every copy on a server, because old virtual environments often linger:
find / -xdev -type d -iname "pillow-*.dist-info" 2>/dev/nullThen check distribution packages and Ghostscript:
dpkg -l python3-pil 2>/dev/null | grep '^ii'
rpm -q python3-pillow
command -v gs && gs --versionIf 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.txtIn 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/appHow 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 '@*' \) -lsAlso 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 everyImage.open()on untrusted input - ☐ Remove Ghostscript from upload servers or set
gs_binary = False - ☐ Apply an ImageMagick policy if you use Wand
- ☐ Add
pip-auditto 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
- GitHub advisory GHSA-w889-685w-c8fh: Argument Injection in Ghostscript call
- GitHub advisory GHSA-vx6w-vv9f-6gvg: EPS load(scale) bomb limit bypass
- Pillow 13.0.0 release notes
- Pillow 12.3.0 release notes (CVE-2026-59203)
- Pillow fix commit a2c3631
- Debian security tracker: CVE-2026-112900
- Ubuntu CVE page: CVE-2026-112900
- The Hacker News: Critical Flaw in Python’s Popular Pillow Image Library
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.
