Pillow 13.0.0 Is the Pin. Image.open Is Not a Preview

OX: three CVSS 9.2 Pillow bugs, disclosed Oct 19, fixed in 13.0.0. 436M downloads. Hacker News: Python Brotli 1.2.1, scanners may miss PYSEC.

OX Security this week: three Pillow bugs, each CVSS 9.2, disclosed 2026-10-19, all fixed in Pillow 13.0.0. A crafted EPS, TIFF, or JPEG can hit command execution or heap corruption in any Python service that processes user-supplied images. OX’s download line: about 436 million monthly downloads. That is a pin. It is not a thumbnail.

We already filed pip 26.2.2 dropping emSign while your OS store did not and LiteLLM 1.107.0 lasting two hours. This week the named wheel is Pillow. The named trap is Image.open on an upload you did not pin.

The Hacker News on October 24 and SecurityWeek on October 26 are the second table: Node2Shell, CVE-2026-111497, Google’s Brotli decoder. Python packages Brotli 1.2.1 and brotlicffi 1.2.1.0. PYSEC-2026-4322 and PYSEC-2026-4323 landed October 23. Dependabot may still show nothing because the CVE names Google’s Brotli repo. pip freeze | grep -i brotli is the check. A scanner silence is not a pin.

13.0.0 is one number. Three CVEs are three paths.

OX’s table is the one to paste into the PR.

CVE-2026-112900: EPS via Ghostscript (EpsImagePlugin), Pillow >= 2.6.0 and < 13.0.0, fixed 13.0.0, 9.2. Pillow shells out to Ghostscript to render EPS. The input filename goes after -f without becoming an absolute path. A name that starts with - is an option to Ghostscript. Combined with @argfile syntax, an attacker who controls the filename Pillow opens can inject Ghostscript options and run commands, even under default -dSAFER. Reachable only when gs is on the host.

CVE-2026-112393: TIFF write (_tiffWriteProc), >= 2.0.0 and < 13.0.0, 9.2. Compressed TIFF saved to a file-like object with no real fileno(), for example io.BytesIO. The grow loop sizes the reallocation from state->size instead of state->loc. Output larger than 64 KiB can write past the heap. Saving to a real file path is not affected.

CVE-2026-112403: JPEG decode (ImagingJpegDecode), all < 13.0.0, 9.2. draft() or thumbnail() on a crafted 8-bit lossless (SOF3) JPEG. The decoder writes a full-width scanline into a buffer sized from tile or draft extents, not from the JPEG header. Newer libjpeg-turbo builds accept malformed input that older 2.x / IJG builds reject.

Three formats. One pin. pip install -U "Pillow>=13.0.0" and rebuild wheels, containers, and base images that vendor it. OX says that is the only complete fix for all three. A lockfile that still says 12.x is the old library with a new comment.

Do not paste a proof of concept into Slack. The advisory is enough. If your pipeline never sees EPS, you still have JPEG thumbnails. If you never call thumbnail(), you still have Image.open. Pin first. Then delete gs from the upload workers.

436 million downloads is not your venv.

OX calls Pillow the most popular Python image library, ~436 million monthly downloads, in Django and Flask image handling, thumbnail and avatar pipelines, document and PDF tooling, and most Python ML and computer-vision stacks, directly or transitively. Your data job does not have to import PIL in the notebook you opened today. The Spark worker that writes previews might.

pip show Pillow on the image that actually runs Image.open. Not on your laptop. The CI image. The Streamlit box. The SageMaker kernel. If that output is 10.x because a base image froze in March, 13.0.0 on your laptop is a blog post.

Transitive is the usual miss. requirements.txt has pandas and matplotlib. Matplotlib pulls Pillow. The freeze still has 10.4.0. Upgrade the freeze, not the README.

EPS is the one people will claim they do not have. Avatar uploads are JPEG. Then a designer drops a .eps from Illustrator into the same bucket because the form accepts “images.” gs is on the box because someone installed texlive in 2023. CVE-2026-112900 is that intersection. OX: keep Ghostscript off hosts that process untrusted uploads. That is cheaper than arguing about -dSAFER.

TIFF-to-BytesIO is the notebook pattern. You open a stack, resize, save(buf, format="TIFF") into memory for a FastAPI response. That is the path with no fileno(). Saving to /tmp/out.tif is the path OX says is not affected. If you needed BytesIO, you needed 13.0.0 last week.

JPEG draft() and thumbnail() are the “we downscale so it is safe” instinct. The bug is in the downscale path. A preview function is not a sandbox. Treat untrusted JPEG plus thumbnail() as sensitive, which is OX’s wording, not a vibe.

Brotli 1.2.1 is a different wheel. Same week.

Node2Shell is CVE-2026-111497, an out-of-bounds write in Google’s Brotli decoder, versions 1.0.3 through 1.2.0. Google CVSS v4 9.2. Fixed Brotli 1.0.10, 1.1.1, 1.2.1. Python: PyPI Brotli 1.2.0 and earlier, patch 1.2.1. brotlicffi 1.2.0.x and earlier, patch 1.2.1.0. SecurityWeek: Node.js and the Python packages patched last week; Microsoft followed October 25 for .NET 8.0.33, 9.0.22, 10.0.14, Visual Studio, PowerShell 7.4–7.6. Microsoft’s own score on that CVE is 8.1, EoP, “Exploitation More Likely” for .NET and VS.

The Hacker News: the fix landed in Brotli source on February 27, and no release carried it until October 21. OpenAI: the root cause was fixed in February, but it was not known then as a vulnerability, and there was no newer Brotli release. Node.js credits Khai Tran and Sylvie Mayer of OpenAI Codex Security, maintainers Matteo Collina and Antoine du Hamel.

Python data jobs hit this when an HTTP client decodes br. Pandas read_csv from a URL, a custom requests session, httpx, anything that asks for Brotli. Dropping br from Accept-Encoding does not help on its own, THN says, because a server can send Brotli anyway. For Node, no workaround, update the runtime. For Python, update the two packages and then check who vendors a copy.

PYSEC-2026-4322 and PYSEC-2026-4323 were added October 23. THN: dependency scanners may not flag the Python packages yet because the CVE record names Google’s Brotli repository, not the PyPI names, and GitHub’s advisory listed no affected package, which is what Dependabot reads. Manual freeze is the control. We keep saying this because the scanner will be a week late.

Debian fixed brotli for Debian 13 (trixie) on October 23 in 1.1.0-2+deb13u1. Amazon Linux 2023 still listed brotli and Node.js 24 as pending on October 22, per THN. Your notebook image might be Amazon. rpm -q brotli is not pip show Brotli. Check both.

SecurityWeek: no exploitation in the wild reported so far. Some researchers argue DoS only; a public PoC reportedly needs a weakened build. That sentence is not a skip. Pin 1.2.1. Do not wait for a blog to call it “just crash.”

The data path is the upload, the preview, and the HTTP read.

A typical Pyrastra-shaped stack: object store, a Python job, Pillow for a contact sheet, pandas for the table, an HTTP client for a vendor CSV that happens to be Brotli. Two libraries. Two pins. Same Tuesday.

Do not disable image uploads as a personality. Disable EPS if you do not need it. Allowlist JPEG and PNG if that is the product. Keep gs off the worker. Stop writing TIFF into BytesIO until 13.0.0 is in the image. Stop calling thumbnail() on bytes you did not produce.

For HTTP: pip show Brotli brotlicffi. If neither is installed, check whether httpx or urllib3 is pulling a vendored decoder. If you are on a distro Python, check the distro brotli. Two layers, like the emSign store we already wrote down.

Rebuild. A pip install in a running container that still has a copied /usr/local/lib from the last image is how 12.x survives the ticket. OX said rebuild wheels, containers, and base images. Believe the base image part.

If you vendor Pillow in a wheel you built in 2025, that wheel is the CVE. 13.0.0 on PyPI does not patch a .whl you host internally until you rebuild it.

Pin, then prove the process, not the blog.

Commands you can run without a story:

python -c "import PIL; print(PIL.__version__)" on the job runner.

pip index versions Pillow if you need to see 13.0.0 exists on the index you actually use. A mirror that lags PyPI is a fourth CVE.

grep -R "Image.open" on the repo. Every hit is a format decision. EPS filenames from users are the 112900 hit. BytesIO TIFF saves are 112393. draft and thumbnail are 112403.

pip freeze | grep -i brotli and the distro package. PYSEC IDs if your scanner wants a ticket number: PYSEC-2026-4322, PYSEC-2026-4323.

Do not open random EPS files to “see if we are vulnerable.” You are. If you are under 13.0.0, OX already did that experiment.

JupyterHub spawners are where the old wheel hides. A user kernel that pip installs Pillow into $HOME will ignore the image pin. Tell them 13.0.0 in the profile, or they will screenshot a CVE blog and keep 10.4.0.

OX disclosed 2026-10-19. If your change window is “next month,” that is ten days of avatar uploads on a 9.2. Emergency pin, then the CAB. Same pattern as LiteLLM’s two-hour release: the version that is on PyPI today is the one you want, not the one in last quarter’s approved list.

Microsoft’s October 25 .NET numbers are for the people who also run C# next to the notebook. 8.0.33, 9.0.22, 10.0.14, VS 2019/2022/2026, PowerShell 7.4, 7.5, 7.6. If the data team only patches Python, the ETL box that shells out to pwsh is still on the old decoder. SecurityWeek called Microsoft’s exploitability “Exploitation More Likely” for .NET and Visual Studio. That is their label. Your label is the freeze file.

Bun 1.4.4, Envoy and Istio Brotli decompressor flags, Node 22.23.4 / 24.22.1 / 26.12.1: only relevant if those run next to the job. The Python line is still 1.2.1 and 1.2.1.0. Do not pin Node and skip Brotli.

Pillow 13.0.0 is the imaging pin. Brotli 1.2.1 is the HTTP pin. 436 million downloads is why this is a data-science story and not a niche CVE. The notebook that only “looks at pictures” is the worker. Pin the worker.

If matplotlib, torchvision, or a document stack pulled Pillow for you, they do not bump it for you. Your freeze does. Same for brotlicffi hiding under an older scientific image. Two pip show lines. One PR. Leave the 12.x comment out of the merge. Tuesday’s job is the pin, not a new contact-sheet feature.

Spread The Article

Share this guide

Send this article to your network or keep a copy of the direct link.

X Facebook LinkedIn Reddit Telegram

Discussion

Leave a comment

No comments yet

Be the first to start the conversation.