Pillow 13.0.0, released on 16 October 2026, fixes four serious bugs in the Python imaging library, including Ghostscript argument injection (CVE-2026-112900) and a FlashPix heap overflow (CVE-2026-112398). Every Pillow release up to 12.3.0 is affected. Upgrade to 13.0.0 everywhere, and if you cannot today, disable EPS and FPX handling and remove Ghostscript from images.
No exploitation of any of these bugs has been reported. The reporters describe working proofs in their own lab environments, but there is no public report of an attack. That gives you a window. This post explains which conditions make each bug reachable, how to find every copy of Pillow you run (including copies hidden inside other wheels and container images), and what to switch off if the upgrade has to wait.
Pillow sits under thumbnailers, document pipelines, machine learning data loaders, report generators and avatar uploads. Most teams never chose it directly; it arrived as a dependency of something else, which is why this release deserves a careful inventory rather than a single pip install.
What happened
| Date and time (UTC, 2026) | Event | Source |
|---|---|---|
| 1 Jul, 11:53 | Pillow 12.3.0 published on PyPI, the last release before the fixes. | PyPI JSON API |
| 29 Aug | The lossless JPEG report (GHSA-8g5g-8vmr-9782) analyses Pillow's main branch at commit e41083f383c9cd3db95de52564cc0b6452313d4a, dated 29 August. | GHSA-8g5g-8vmr-9782 |
| 16 Oct | Pillow 13.0.0 release notes are dated 16 October and list the security fixes by GHSA ID and severity. | Pillow 13.0.0 release notes |
| 16 Oct, 22:15 to 22:18 | 83 files for Pillow 13.0.0 uploaded to PyPI, ending with the source tarball at 22:18:09. | PyPI JSON API |
| 17 Oct, 10:34:28 | GHSA-8g5g-8vmr-9782 (lossless JPEG decode overflow) published. | GitHub advisory API |
| 17 Oct, 10:39:33 | GHSA-w889-685w-c8fh (EPS filename argument injection into Ghostscript) published. | GitHub advisory API |
| 17 Oct, 10:54:43 | GHSA-332p-7hrc-92wf (TIFF save to BytesIO heap overflow) published. | GitHub advisory API |
| 17 Oct, 20:36:23 | GHSA-5x8w-8jj5-q52h (FlashPix JPEG tile heap overflow) published. | GitHub advisory API |
| 19 Oct, 20:46 to 22:07 | OSV publishes records for CVE-2026-112393 (20:46:40), CVE-2026-112398 (21:07:25), CVE-2026-112403 (21:22:21) and CVE-2026-112900 (22:07:50). | OSV API |
| 19 Oct, 21:17 to 22:17 | NVD publishes CVE-2026-112393 (21:17:07), then CVE-2026-112398, CVE-2026-112403 and CVE-2026-112900 (22:17:07 to 22:17:10). | NVD API |
| 20 Oct, 19:48 and 19:50 | NVD modifies the CVE-2026-112393 and CVE-2026-112900 records. All four remain "Undergoing Analysis". | NVD API |
| 22 Oct, 21:17 and 22:18 | NVD modifies the CVE-2026-112398 and CVE-2026-112403 records. | NVD API |
| 23 Oct | OSV records last modified (02:45 and 13:06). No newer Pillow release than 13.0.0 on PyPI, and no 12.x backport. | OSV API, PyPI JSON API |
The release went out first and the advisories followed the next day, the usual order for a coordinated fix. CVE identifiers came two days later.
The four CVEs at a glance
| CVE | GHSA | Bug | Advisory severity | CVSS 4.0 in CVE record | Affected | Fixed |
|---|---|---|---|---|---|---|
| CVE-2026-112900 | GHSA-w889-685w-c8fh | Argument injection into the Ghostscript command line when opening EPS | Medium | 9.2 | 2.6.0 to 12.3.0 | 13.0.0 |
| CVE-2026-112398 | GHSA-5x8w-8jj5-q52h | Heap buffer overflow in FlashPix (FPX) JPEG tile decoding | Critical | 9.2 | all versions up to 12.3.0 | 13.0.0 |
| CVE-2026-112403 | GHSA-8g5g-8vmr-9782 | Heap buffer overflow in ImagingJpegDecode for lossless (SOF3) JPEG when downscaling | High | 9.2 | all versions up to 12.3.0 | 13.0.0 |
| CVE-2026-112393 | GHSA-332p-7hrc-92wf | Heap buffer overflow and stale heap disclosure when saving compressed TIFF to an in-memory target | High | 9.2 | 2.0.0 to 12.3.0 | 13.0.0 |
The CVSS 4.0 scores come from GitHub as the CVE Numbering Authority; NVD shows them as a "Secondary" source and has not published its own assessment.
These four are not the whole release. The 13.0.0 notes reference 42 GHSA identifiers across Critical, High, Medium and Low, covering TIFF, PDF, GIF, WMF and EMF, BMP, SPIDER, APNG, fonts and bundled OpenJPEG. This post focuses on the four with the most direct path to code execution. The upgrade fixes all of them at once.
Technical details
Identifiers, weaknesses and fix commits
| Field | CVE-2026-112900 | CVE-2026-112398 | CVE-2026-112403 | CVE-2026-112393 |
|---|---|---|---|---|
| GHSA | GHSA-w889-685w-c8fh | GHSA-5x8w-8jj5-q52h | GHSA-8g5g-8vmr-9782 | GHSA-332p-7hrc-92wf |
| Advisory title | Argument Injection in Ghostscript call, potentially leading into RCE | Heap buffer overflow in FlashPix (FPX) JPEG decoding leads to code execution | Heap buffer overflow in ImagingJpegDecode for lossless JPEG after draft() scaling | Heap buffer overflow in _tiffWriteProc when saving TIFF to BytesIO |
| CWE (advisory) | CWE-78, CWE-88 | CWE-122, CWE-787 | CWE-122 | CWE-122 |
| CWE (NVD) | CWE-78 | CWE-122 | CWE-122 | CWE-122 |
| CVSS 4.0 vector (CNA) | AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N | AV:N/AC:H/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N | AV:N/AC:H/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N | AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N |
| Fix commit (NVD, OSV) | a2c363141da31a265dd536c945677db4b64f3ec9 | 242a059aedc1935097d9aa1dc3f099750467ba75 | 242a059aedc1935097d9aa1dc3f099750467ba75 | 11582e5ffbdceb973d5ee593f3105af5a9852b40 |
| 13.0.0 tag commit (OSV) | 293218ad059d994baf1777d882911bed6a49704a | same | same | same |
| Introduced (OSV) | 9634e437efeeda906ad6bfcc275b17732d64f32a | from the start (0) | from the start (0) | baea361b9a29e9a1dfa5a9b8e11bc9624a9cee46 |
Two of the four share a fix commit. That matches the release notes, which group GHSA-5x8w-8jj5-q52h and GHSA-8g5g-8vmr-9782 into a single Critical entry titled "JPEG decoder buffer overflow from scanlines wider than tile extents". Both bugs come down to the JPEG decoder writing more bytes per row than the buffer Pillow allocated for that row.
Release artifacts
| Item | Value | Source |
|---|---|---|
| Fixed version | 13.0.0 | PyPI, release notes |
| Files on PyPI | 83 (wheels plus source tarball) | PyPI JSON API |
| Source tarball | pillow-13.0.0.tar.gz | PyPI JSON API |
| Source tarball SHA-256 | cdcd1ba258178f7356d74170c9a81ba4eab6a5d55748bc402a5a2a6fe6be3510 | PyPI JSON API |
| Requires Python | >=3.11 | PyPI JSON API |
Optional extras that pull in olefile | fpx, mic (also docs, tests) | PyPI metadata |
| Previous release | 12.3.0, uploaded 1 July 2026 | PyPI JSON API |
The requires_python line matters. Pillow 13.0.0 drops Python 3.10, which the release notes say reached end of life in October 2026, and there is no 12.x backport on PyPI. A Python 3.10 service must move its interpreter first.
CVE-2026-112900: EPS filename argument injection into Ghostscript
What it is. To render an EPS image, EpsImagePlugin runs the external Ghostscript program (gs). According to the advisory, the input file name went into Ghostscript's argument list as it was. Ghostscript reads an argument that starts with a hyphen as an option, so a file name chosen by an attacker could add an option of their choosing. NVD's description adds that full command execution "specifically requires" Ghostscript's argument file syntax in the name plus control over the referenced text file.
Why -dSAFER does not stop it. Pillow already runs Ghostscript in SAFER mode. The advisory reports the injection works with that default, on Ghostscript 10.02.1 and 10.07.1. SAFER limits what a PostScript document can do; this bug lives on the command line.
Why this is not the old EPS warning. Pillow's security policy already says to block EPS input unless you need it. The reporter's point is that this bug is triggered by the file name, not the content: an ordinary EPS file is enough if the name passed to Image.open() is attacker influenced. Teams that legitimately accept EPS are the ones this bug was really about.
Conditions, all of which must hold:
- Pillow 2.6.0 through 12.3.0.
- Ghostscript installed and findable. Pillow looks for
gs, and on Windows alsogswin32candgswin64c. - The application passes a path string to
Image.open()and the attacker can influence it, for example an upload endpoint that keeps the user's original file name. - The file is an EPS that Pillow renders with Ghostscript.
The advisory's code excerpt shows that when Pillow gets a buffer or a file object without a usable name, it writes a temporary file it names itself, so the attacker never chooses the name. The fix, commit a2c363141da31a265dd536c945677db4b64f3ec9, turns the name into an absolute path so it can never be read as an option.
CVE-2026-112398: FlashPix JPEG tile heap overflow
What it is. FlashPix (FPX) is an old Kodak format stored inside a Microsoft OLE2 compound file. Pillow's format documentation says: "To enable FPX support, you must install olefile." The olefile documentation describes that package as a parser for OLE2 files, FlashPix among them.
Inside an FPX file, a JPEG tile carries color information that Pillow maps to a "raw mode", which sets how many bytes per pixel the output buffer holds. The advisory explains that some combinations map to raw modes the JPEG decoder does not recognize, so it falls back to emitting as many bytes per pixel as the embedded JPEG declares, more than the buffer holds. The result is a heap out-of-bounds write whose length and content come from the file.
Why it is rated Critical. Pillow recognizes FPX by content (the OLE2 signature), not extension, so a file named avatar.png is still parsed as FPX. And the overflow happens during the ordinary Image.open() and load() sequence every upload service runs. The advisory reports command execution from one crafted file on stock Pillow 12.2.0 in a pinned lab setup. We do not reproduce that detail.
Conditions:
- Any Pillow version up to 12.3.0.
olefileimportable in the same Python environment. It can arrive throughpillow[fpx]orpillow[mic], or as a dependency of some unrelated package that parses Office files.- The application opens files from untrusted sources and does not restrict which formats
Image.open()may try.
If olefile is not installed, Pillow cannot open FPX at all and this path is closed. That makes it one of the easiest conditions to check and to remove.
CVE-2026-112403: lossless JPEG overflow when downscaling
What it is. thumbnail() first calls draft() on JPEG images to ask the JPEG library for a 1/2, 1/4 or 1/8 scale decode, then allocates a row buffer for the smaller width. According to the advisory, Pillow never checked the output width the library actually reported before reading rows into that buffer. libjpeg-turbo 3.x decodes lossless JPEG (the SOF3 marker) and, the advisory says, silently ignores scaling for lossless streams. Full width rows then land in a buffer sized for a fraction of the width.
Why format allowlists do not help here. The input is a standards compliant JPEG, and the advisory notes that restricting Image.open() to JPEG does not prevent it.
Conditions:
- Any Pillow version up to 12.3.0.
- Pillow linked against libjpeg-turbo 3.x. NVD's description says libjpeg-turbo 2.x and IJG libjpeg builds reject the lossless input. Note that the default Linux wheels qualify: the CycloneDX SBOM inside the official 12.3.0 manylinux wheel we inspected lists libjpeg-turbo
3.1.4.1. - The application downscales JPEG input through
draft(), whichthumbnail()calls automatically. The advisory's example: withthumbnail((256, 256))and the default reducing gap, images of at least 1024 by 1024 pixels trigger a scaled decode.
Credit. The advisory's report text says the issue "was found by Anthropic using Claude to study the security of open-source software", and that it was validated manually by a third party. We name only the credit line, as the advisory gives it.
CVE-2026-112393: TIFF save to BytesIO overflow and heap disclosure
What it is. This one is on the save path. When Pillow writes a compressed TIFF through libtiff to a target with no real file descriptor, such as io.BytesIO or a storage stream, it gives libtiff an in-memory "file". The advisory explains that Pillow's write callback grew that buffer from its previous size rather than from the current write position, so a write after libtiff seeks forward could land past the allocation. Newly grown memory was also never cleared.
Two separate effects. A heap overflow, which needs an attacker controlled tag value to reach libtiff, either because the opened TIFF is re-saved directly or through an embedded ICC profile, which the advisory says survives copy(), convert(), resize() and thumbnail(). And a small information leak needing no crafted tag: compressed TIFF output over 64 KiB saved this way can carry stale heap bytes in its alignment gaps.
Conditions:
- Pillow 2.0.0 through 12.3.0. The advisory traces the defect to a 2013 merge first released in Pillow 2.0.0.
- The application saves TIFF output with compression to an in-memory or stream target. Saving to a file path is not affected, because the operating system zero fills skipped bytes and the buggy callback is not used.
- For the overflow, attacker controlled tag data or an ICC profile carried from an uploaded image into the save.
The advisory states that the overflow on its own crashed the process in the reporter's test and describes a code execution chain that also needed addresses leaked by a separate bug. Treat it as serious, but it has more preconditions than the FPX bug.
What the bug classes have in common
Three of the four are one class of mistake: a native buffer sized from one assumption (a requested scale, a raw mode, a previous size) and filled from a different source of truth (the decoder's real output, the file's declared components, libtiff's write position). The fourth is the classic shell-out mistake: data placed where the child program reads it as an instruction.
Exploitation status and indicators
No source we read reports exploitation in the wild, and none of the four CVEs appears in CISA's Known Exploited Vulnerabilities catalog version 2026.10.21, released 21 October. The advisories, NVD and OSV list no indicators of compromise. Useful hunting signals are behavioural: image workers that crashed with C library heap corruption messages, gs processes with unexpected arguments, and uploads stored under names beginning with a hyphen.
Where sources differ
- Severity. The advisory and the release notes rate CVE-2026-112900 Medium, while its CVE record, written by GitHub as CNA, carries a CVSS 4.0 score of 9.2 (Critical). GHSA-8g5g-8vmr-9782 is High on the advisory but listed under Critical in the release notes, and its CVE record also says 9.2. Our reading: the labels reflect how likely the preconditions are, and the vectors score the case where they hold. Do not use the Medium label to defer the EPS fix if you accept EPS with user influenced names.
- NVD's own analysis. As of 23 October all four records are "Undergoing Analysis"; the 9.2 scores are not NVD's assessment.
- Where the TIFF bug started. The advisory names commit
e782fe721e01from March 2013; OSV recordsbaea361b9a29e9a1dfa5a9b8e11bc9624a9cee46. Both point to Pillow 2.0.0 as the first affected release. - CVE IDs in the release notes. When we fetched the notes, the four issues were listed by GHSA ID only. Search your scanners for both identifiers.
How it works, in plain language
Pillow is a Python layer that recognizes formats, on top of a C layer and bundled native libraries such as libjpeg-turbo and libtiff that do the pixel work. Python protects you from most memory mistakes; the C layer does not. When a row buffer holds 600 bytes and the decoder writes 1,200, the extra bytes overwrite whatever sits next to it, and a skilled attacker can sometimes turn that into running code.
Pillow is also generous about formats by design. Image.open() tries every plugin unless told otherwise. EPS needs Ghostscript on the machine; FPX needs olefile in the environment. If those extras are present, the plugins are live whether or not anyone knew.
So the risk is shaped by three quiet questions. Is Ghostscript in the image? Is olefile in the environment? Which formats does our code let Image.open() try?
Who is affected
| You run | Exposed to | Notes |
|---|---|---|
Pillow 12.3.0 or earlier, accepting uploads, olefile installed | CVE-2026-112398 | Highest priority. Detection is by content, so file extension checks do not help. |
| Pillow 12.3.0 or earlier, making thumbnails of JPEG uploads, libjpeg-turbo 3.x | CVE-2026-112403 | Format allowlists that include JPEG do not help. |
| Pillow 2.6.0 to 12.3.0, Ghostscript installed, user influenced file names | CVE-2026-112900 | Severity labels differ; treat as urgent if the conditions hold. |
| Pillow 2.0.0 to 12.3.0, saving compressed TIFF to BytesIO or streams | CVE-2026-112393 | File path saves are not affected. |
| Pillow 13.0.0 | None of the four | Confirm every copy, not just the main application. |
| Python 3.10 services | Cannot install 13.0.0 | Move to Python 3.11 or later, then upgrade. |
Scripts that only open your own files are low risk. Anything processing files from customers, email, partners or the internet is the priority.
What to do now
1. Upgrade Pillow to 13.0.0
pip, with a requirements file:
python -m pip install --upgrade "pillow>=13.0.0"
python -c "import PIL; print(PIL.__version__)"
Pin it so an old lockfile cannot bring 12.x back:
# requirements.txt or constraints.txt
pillow>=13.0.0
Poetry:
poetry add "pillow>=13.0.0"
poetry show pillow
If Pillow is transitive, poetry update pillow moves it within your other packages' constraints; poetry show --tree reveals anything capping it below 13.
uv:
uv lock --upgrade-package pillow
uv sync
uv pip show pillow
uv's documentation notes that it prefers the previously locked versions on uv sync and uv lock, so a plain sync will not move you off 12.3.0. You need the explicit upgrade.
Then rebuild every container image and redeploy. A repository that says 13.0.0 does nothing for an image built last month.
2. Find every copy in your estate
This is the step most teams underdo. Work through each layer.
Developer and CI environments.
python -m pip show pillow
python -m pip list --format=freeze | grep -i '^pillow=='
python -m PIL | head -n 20
The Pillow documentation points to python -m PIL (or PIL.features.pilinfo()) for listing available formats; its feature summary also shows which JPEG library the build uses, which matters for CVE-2026-112403.
Lockfiles across all repositories. Search lockfiles, not manifests. A constraint such as pillow>=9 says nothing about what is installed.
# requirements files compiled by pip-tools or exported from other tools
grep -RniE '^pillow==' --include='requirements*.txt' .
# poetry.lock
grep -RnA1 -E '^name = "pillow"' --include=poetry.lock .
# uv.lock
grep -RnA1 -E '^name = "pillow"' --include=uv.lock .
# Pipfile.lock
find . -name Pipfile.lock -exec jq -r '.default.pillow.version // empty' {} \;
Any result below 13.0.0 needs a change. Package names in Python are case insensitive, so Pillow and pillow are the same project; the commands above match both.
Container images. Check the image, not the Dockerfile.
IMAGE=registry.example.com/thumbnailer:latest
docker run --rm --entrypoint sh "$IMAGE" -c '
find / -xdev \( -iname "pillow-*.dist-info" -o -name "_imaging*.so" \) 2>/dev/null
command -v gs gswin64c 2>/dev/null
python3 -c "import olefile; print(\"olefile\", olefile.__version__)" 2>/dev/null
'
That one pass answers three questions: which Pillow versions are present (the packaging specification for installed projects puts the version in the .dist-info directory name), whether Ghostscript is there, and whether olefile imports. Operating system packages are a separate source; on Debian based images also run dpkg -l | grep -i python3-pil and check your distribution's tracker.
Vendored copies inside wheels and bundles. Some projects ship Pillow inside their own wheel, a frozen application, a serverless layer or a zip app. Those copies do not appear as pillow in pip list. Look for Pillow's compiled module instead:
# installed environments: any _imaging extension not under a top level PIL package
find / -xdev -name "_imaging*.so" -o -name "_imaging*.pyd" 2>/dev/null | grep -v '/site-packages/PIL/'
# wheel files and zip bundles on disk or in an artifact store
for f in $(find . -name "*.whl" -o -name "*.zip"); do
unzip -l "$f" 2>/dev/null | grep -qE '(^|/)PIL/_imaging|pillow\.libs/' && echo "$f"
done
A wheel is a zip archive whose .dist-info/RECORD file lists every installed file, so a vendored PIL directory shows up in the host project's RECORD. Pillow's own Linux wheels keep bundled native libraries in pillow.libs/, and the 12.3.0 wheel we inspected carries CycloneDX SBOMs under pillow-12.3.0.dist-info/sboms/.
SBOMs you already have. Query them for pkg:pypi/pillow below 13.0.0; that is usually faster than rescanning. See finding affected products fast with SBOMs.
3. Check the three conditions for each copy you cannot upgrade today
For each environment still below 13.0.0, record:
command -v gs && gs --version # CVE-2026-112900 needs this
python3 -c "import olefile" && echo "olefile present" # CVE-2026-112398 needs this
python3 -m PIL | grep -i jpeg # CVE-2026-112403 needs libjpeg-turbo 3.x
Then read the code paths: does anything call Image.open() with a path built from user input; does anything call thumbnail() or draft() on uploads; does anything save TIFF to a BytesIO or response stream.
4. Retest after the upgrade
13.0.0 is a major release. The notes list backwards incompatible changes: Python 3.10 dropped, 32-bit I mode images no longer silently clipped when saved as PNG, Image._show removed, and duplicate TIFF tags now keeping the first value. Wheels also now link bundled HarfBuzz, which may slightly change text rendering. Run your image tests before rollout.
Mitigations if you cannot upgrade at once
Use these to buy days, not months. They reduce exposure; they do not fix the four bugs or the other advisories in the release.
Turn off Ghostscript inside Pillow
Pillow's format documentation says that setting EpsImagePlugin.gs_binary = False prevents Ghostscript use. Put it at application start, before any image is opened:
from PIL import EpsImagePlugin
EpsImagePlugin.gs_binary = False
This closes CVE-2026-112900 in that process. EPS files that need rendering will then fail to load, which is usually what you want for an upload service.
Remove Ghostscript from images that do not need it
The stronger version is to make sure gs is not in the image at all:
# Debian or Ubuntu base
RUN apt-get purge -y ghostscript && apt-get autoremove -y
# Alpine base
RUN apk del ghostscript
# Fedora, RHEL or similar base
RUN dnf remove -y ghostscript
Then verify in the built image with command -v gs. If removing it breaks another tool, the image was doing more than one job, and splitting it is worth considering.
Never let users choose the file name Pillow sees
Store uploads under names your application generates, or pass Pillow a file object or bytes buffer instead of a path. That removes the attacker controlled name CVE-2026-112900 requires.
Disable FPX by removing olefile, or by allowlisting formats
If nothing in the environment needs olefile, remove it:
python -m pip show olefile # check "Required-by" first
python -m pip uninstall -y olefile
If something does need it, restrict what Image.open() will try. The Pillow reference documents a formats argument for exactly this purpose:
from PIL import Image
ALLOWED = ("JPEG", "PNG", "WEBP", "GIF")
with Image.open(upload_stream, formats=ALLOWED) as im:
im.load()
An allowlist without EPS, FPX and MIC closes CVE-2026-112398 and the Ghostscript path at that call site, but not CVE-2026-112403, whose input is a valid JPEG. Apply it at every call site that handles untrusted input.
Reduce the TIFF save exposure
Save TIFF renditions to a temporary file path rather than a BytesIO, then stream the file; the advisory states file path saves are unaffected. Dropping embedded ICC profiles and descriptive tags from untrusted images before re-saving also removes the data the overflow needs.
Isolate the lossless JPEG path
There is no clean switch for CVE-2026-112403 on 12.x. The practical stopgap is the one Pillow's security policy recommends for high risk deployments: process images in a sandboxed subprocess with no network access, a read only file system and minimal privileges. Then upgrade.
Python 3.10 services
There is no 13.0.0 for Python 3.10 and no 12.x backport. Apply every mitigation above, schedule the interpreter upgrade, and track these services by name.
What would have helped
An inventory that includes optional extras and system binaries
Most dependency inventories list Python packages. Few record olefile as the reason a format is live, and almost none record whether gs sits in the same image. Two of these four bugs depend entirely on those neighbours. An inventory that answers "which images contain Pillow below 13.0.0 and Ghostscript" in one query turns this into a short afternoon.
Format allowlists, generated names and slim images
Services that already passed formats= to Image.open() were not exposed to the FPX bug through that call. Applications that never let a user choose a name on disk were never exposed to CVE-2026-112900. And a thumbnailer built on a minimal base image does not carry Ghostscript at all. None of these is new advice; each would have removed a precondition here.
Interpreter upgrades on a schedule
Pillow 13.0.0 requires Python 3.11 or later. Teams that left Python 3.10 before its end of life apply this fix with one bump. Teams that did not now face two changes at once under pressure.
Sandboxed image processing and routine lockfile refresh
Pillow's security policy recommends a sandboxed subprocess for high risk deployments; memory bugs in image parsers will keep appearing, and a sandbox decides how much one can reach. And because uv keeps locked versions unless asked to move, a scheduled job that proposes lockfile updates with tests keeps the gap between "fix published" and "fix running" measured in days.
Where Safeguard fits
Safeguard does not change how Pillow parses images, and nothing here would have prevented these bugs. It helps with the operator's problem: knowing where Pillow runs, at which version, and moving it to 13.0.0 quickly.
- Inventory and matching. Safeguard scans repositories, container images and SBOM files (
safeguard scan --dir .,safeguard scan --image <name>,safeguard scan --sbom <file>, with JSON results under./output), including PyPI packages and container layers. The platform shows which projects contain a given component and version, so "which images resolve pillow below 13.0.0" becomes a query. Matching draws on several databases, including NVD, GHSA and OSV. - Prioritisation. Reachability analysis helps rank findings by whether your code reaches the vulnerable paths. It cannot tell you whether Ghostscript is installed on a host you do not scan, or whether a user controls a file name, so confirm those conditions with the commands above.
- Policies and gates. A policy can fail a build that resolves pillow below 13.0.0, so an old lockfile on a long lived branch cannot bring it back.
- Fixes with approval. The Safeguard CLI's assistant can edit
pyproject.tomlor a requirements file, runuv lock,poetry updateorpip install, and run your tests, asking before each command. Autonomous remediation can open fix pull requests, and ships switched off by default.
Where Safeguard does not help: it does not remove Ghostscript from a host it is not scanning, it cannot judge whether a crash in your logs was an attack, and it cannot patch Pillow inside a vendor's appliance or hosted service. It does not claim to detect malicious files.
Zero-Day Discovery and Gold
Safeguard's Zero-Day Discovery looks for previously unknown flaws before a CVE exists, in an agentic mode and a deterministic scanner mode, and issues findings as SGZ advisories before they reach public CVE databases. It covers seven package ecosystems. These Pillow bugs were reported to the Pillow maintainers by outside researchers credited on each advisory and fixed by the maintainers; Safeguard had no part in finding them.
Gold Open Source at gold.safeguard.sh is free and needs no login; it shows CVSS, EPSS, KEV status, affected and fixed versions and a recommended safe version for a CVE, through the site, a free read API and a CI gate GitHub Action. We have not confirmed that it holds records for these four CVEs at the time of writing, so check the site rather than relying on this post. Gold Registry at registry.safeguard.sh provides hardened, signed components and container images held to zero exploitable critical or high CVEs; we make no claim that it carries a Pillow build with these fixes. The habit worth taking from both is the one this release rewards: pull patched, signed components from a curated source and rebuild fast.
Common mistakes
- Checking only top level requirements. Pillow is usually transitive. Read the lockfile or the installed environment.
- Upgrading the repository, not the image. An image built from a 12.3.0 lockfile runs 12.3.0 until rebuilt and redeployed.
- Trusting file extensions. FPX is detected by content.
- Assuming "we only accept JPEG" is enough. It does not stop the lossless JPEG overflow.
- Forgetting vendored copies. Frozen apps, serverless layers and wheels that bundle
PILdo not show up aspillowinpip list. - Missing the Python 3.10 tail. pip quietly stays on 12.3.0 there under a loose constraint. Pin
pillow>=13.0.0so the install fails loudly. - Removing Ghostscript from the Dockerfile but not the base image. Check
command -v gsin the built image.
Open questions
- NVD has not finished its own analysis. The 9.2 scores could change.
- No KEV listing or exploitation report exists for any of the four as of 23 October; watch for one.
- Distribution packages of Pillow need their own updates or backports; check your distribution's tracker for each CVE.
- Several of the other advisories in the release had no CVE ID when we read the notes and may receive one later.
FAQ
Is any of this being exploited?
No source we read reports exploitation in the wild for CVE-2026-112900, CVE-2026-112398, CVE-2026-112403 or CVE-2026-112393. The advisories describe lab proofs by the reporters. Upgrade before that changes.
Which version do I need?
Pillow 13.0.0 or later. There is no fixed 12.x release.
We run Python 3.10. What now?
13.0.0 requires Python 3.11 or later. Apply the mitigations in this post, especially removing Ghostscript, removing olefile or allowlisting formats, and sandboxing, then move to Python 3.11 or later and upgrade.
We do not have Ghostscript installed. Are we safe?
From CVE-2026-112900, yes. The other three bugs do not involve Ghostscript, so you still need to upgrade.
We never use FlashPix. Why does CVE-2026-112398 matter?
Because you do not choose the format; the file does. If olefile is importable and your code lets Image.open() try every format, an FPX file with any name will be parsed.
How do I know if olefile is installed?
Run python -m pip show olefile in the environment, or try importing it. Check the "Required-by" line to see what brought it in.
Does restricting formats fix everything?
No. It closes the FPX and EPS paths at that call site. The lossless JPEG overflow is reachable through a valid JPEG, and the TIFF bug is on the save path.
We only save TIFF files to disk. Are we affected by CVE-2026-112393?
The advisory says saves to file paths and real file descriptors are unaffected. Saves to BytesIO or streaming targets are the risk.
Did Safeguard find these vulnerabilities, or have a patched build first?
No. They were reported to and fixed by the Pillow maintainers. Safeguard's role is helping you find every affected copy and move it to 13.0.0.
Fix it without waiting for upstream
For Pillow the upstream fix already exists, so the gap that matters is the first one: how long 13.0.0 takes to reach every image and lockfile you ship.
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.
3. Gold Registry: start from components that are already fixed
registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would have helped
These are real product sessions on other examples; none of them shows this Pillow release.
The CLI demo shows the assistant fixing a lodash CVE: it reads the finding and advisory, asks before each command, edits package.json to lodash 4.17.21, runs npm install and npm test with approval, and pushes nothing. For this release, the same flow applies to a Python service: ask it to move pillow to 13.0.0 in pyproject.toml, approve uv lock or poetry update pillow, then approve your test run before anything is committed.
Click through it at your own pace, or open it full screen.
The desktop app demo shows a CLI session in the conversation list, a request for work on the computer, a folder choice, and Approve or Deny on each command and file edit, ending with an upgrade done and tests passing. Here you would pick the folder of your thumbnail service, ask it to bump Pillow and add gs_binary = False and a formats allowlist at the upload handler, and approve each edit and each command one by one.
Click through it at your own pace, or open it full screen.
The Claude demo shows connecting Claude to the Safeguard MCP server with OAuth and choosing which tools Claude can use. Once connected, the estate wide question for this release, which projects and images resolve pillow below 13.0.0, is something you can ask in plain language against the projects already scanned in your account.
Click through it at your own pace, or open it full screen.
To try it on your own projects, download Safeguard and read the CLI documentation. Related reading: container image supply chain incidents in 2026, AI found vulnerabilities in PHP2Shell and PwnEscape and our October 2026 roundup.
Quick check
Your thumbnail service runs Pillow 12.3.0 with no Ghostscript and no olefile. Is it safe to wait?
No. Without Ghostscript and olefile, the EPS and FPX bugs are closed, but the lossless JPEG overflow (CVE-2026-112403) still applies if Pillow links libjpeg-turbo 3.x and you call thumbnail(). Upgrade to 13.0.0.
You ran uv sync after the release and uv.lock still says pillow 12.3.0. Why?
uv keeps previously locked versions unless your constraints exclude them. Run uv lock with an upgrade for pillow, then uv sync, and pin pillow at 13.0.0 or later.
Sources
- GitHub advisory GHSA-w889-685w-c8fh (CVE-2026-112900)
- GitHub advisory GHSA-5x8w-8jj5-q52h (CVE-2026-112398)
- GitHub advisory GHSA-8g5g-8vmr-9782 (CVE-2026-112403)
- GitHub advisory GHSA-332p-7hrc-92wf (CVE-2026-112393)
- Pillow 13.0.0 release notes
- PyPI: pillow
- PyPI JSON API for pillow
- NVD: CVE-2026-112900
- NVD: CVE-2026-112398
- NVD: CVE-2026-112403
- NVD: CVE-2026-112393
- OSV: CVE-2026-112900
- OSV: CVE-2026-112398
- OSV: CVE-2026-112403
- OSV: CVE-2026-112393
- CISA Known Exploited Vulnerabilities catalog (JSON feed)
- Pillow 12.3.0 manylinux wheel for CPython 3.12 (bundled SBOM inspected)
- Pillow security policy
- Pillow documentation: Image file formats (EPS, FPX, MIC)
- Pillow documentation: Image module reference (Image.open formats)
- Pillow documentation: Installation
- Ghostscript documentation: Using Ghostscript
- olefile documentation
- Python packaging: Binary distribution format (wheel)
- Python packaging: Recording installed projects
- uv documentation: Locking and syncing
- Poetry documentation: Commands
Last reviewed: 23 October 2026. This is a developing story; check the linked advisories for the latest status.