Skip to content
Back to top
View this page

# Security

Pillow’s primary attack surface is parsing untrusted image data. This page documents the threat model for developers integrating Pillow into applications that handle images from untrusted sources, along with recommended mitigations.

To report a vulnerability see Reporting a vulnerability.

## Threat model (STRIDE)

The analysis below follows the STRIDE framework and covers the boundary between untrusted image input and the Pillow API.

                     ┌──────────────────────────────────────────┐
    Untrusted zone   │             Pillow API                   │
    ─────────────    │                                          │
    Image files ────►│  Image.open()  ──►  Format plugins       │
    Byte streams     │  (40+ parsers)      (Python + C FFI)     │
    User metadata    │                                          │
                     │  ImageMath.unsafe_eval(expr)  ───────────┼──► Python eval()
                     │  ImageShow.show(image)  ─────────────────┼──► os.system / subprocess
                     │  EpsImagePlugin.open(eps)  ──────────────┼──► Ghostscript (gs)
                     └──────────────┬───────────────────────────┘
                                    │ C extensions:
                                    │  _imaging · _imagingft · _imagingcms
                                    │  _webp · _avif · _imagingtk
                                    │  _imagingmath · _imagingmorph
                                    ▼
                     ┌──────────────────────────────────────────┐
                     │  C libraries (bundled or system)         │
                     │  libjpeg · libpng · libtiff · libwebp    │
                     │  openjpeg · freetype · littlecms2        │
                     └──────────────────────────────────────────┘

### Spoofing

S-1 — Format sniffing bypass

`Image.open()` detects format by magic bytes, not file extension or MIME type. An attacker can name a file `safe.png` while its content is TIFF, JPEG 2000, or EPS, causing a different — potentially more dangerous — parser to run.

Mitigations: validate MIME type and magic bytes independently before calling `Image.open()`; pass the `formats` argument with an allowlist of accepted formats.

S-2 — Plugin registry spoofing

Pillow’s format registry is a global mutable dictionary. A malicious package installed in the same environment could register a replacement parser for a well-known format.

Mitigations: use isolated virtual environments with pinned, hash-verified dependencies; audit `Image.registered_extensions()` at startup.

### Tampering

T-1 — Malicious metadata propagation

Pillow preserves EXIF, XMP, IPTC, ICC profiles, and comments when round-tripping images. Applications that store or render metadata without sanitisation are vulnerable to second-order injection (SQLi, XSS, command injection).

Mitigations: treat all values from `image.info`, `image._getexif()`, `image.getexif()`, and `image.text` as untrusted; sanitise before storing or rendering; strip metadata when it is not required.

T-2 — Covert data channel (steganography)

Pillow does not remove hidden data (JPEG comments, PNG text chunks) when re-saving. An attacker can embed data that survives the encode-decode cycle invisibly.

Mitigations: to guarantee a clean output when saving, create a new image instance via `image.copy()` and delete the `image.info` contents.

T-3 — Supply chain tampering

Pre-compiled wheels bundle libjpeg-turbo, libpng, libtiff, libwebp, openjpeg, freetype, littlecms2, and other libraries. A compromised PyPI release or build pipeline could ship malicious binaries.

Mitigations: pin with hash verification (`python3 -m pip install --require-hashes`); monitor Pillow security advisories; use Dependabot or OSV-Scanner for bundled C library CVEs.

### Repudiation

R-1 — No structured audit trail

Without application-level logging there is no record of which images were opened, what formats were detected, or what operations were performed, making forensic investigation harder after an incident.

Mitigations: log the filename/hash, detected format, and dimensions of every image processed; log and alert on `Image.DecompressionBombWarning`, `Image.DecompressionBombError`, and `PIL.UnidentifiedImageError`.

### Information disclosure

I-1 — Metadata in saved images

GPS coordinates, author names, software version strings, and ICC profiles can be inadvertently included in output images served publicly.

Mitigations: explicitly strip EXIF and XMP on save (set `exif=b""`, `icc_profile=None`, omit `pnginfo`); verify output with `exiftool` in CI.

I-2 — Temporary file exposure

Several code paths write pixel data to temporary files via `tempfile.mkstemp()`. Exception paths can leave these files behind on shared filesystems.

Mitigations: files are created with mode `0o600`; mount `/tmp` as a per-container `tmpfs`; ensure `try/finally` cleanup is in place.

### Denial of service

D-1 — Decompression bomb

A small compressed image can expand to gigabytes in memory. `PIL.Image.MAX_IMAGE_PIXELS` raises `Image.DecompressionBombError` at 2× the limit and `Image.DecompressionBombWarning` at 1×. PNG text chunks are separately capped by `PngImagePlugin.MAX_TEXT_CHUNK` and `MAX_TEXT_MEMORY`. Check the values in your installed Pillow version at runtime or in the reference/source for the current defaults.

Mitigations: never set `Image.MAX_IMAGE_PIXELS = None` in production; treat `Image.DecompressionBombWarning` as an error; set OS/container memory limits per worker.

D-2 — CPU exhaustion

Large-but-legal images (within `MAX_IMAGE_PIXELS`) can still saturate CPU through high-quality resampling, convolution filters, or complex draw operations.

Mitigations: apply per-request CPU time limits; set a practical dimension ceiling below `MAX_IMAGE_PIXELS`; rate-limit processing requests.

D-3 — Algorithmic complexity in parsers

Formats such as TIFF (nested IFD chains), animated GIF/WebP (many frames), and PNG (many text chunks) can exhaust CPU or memory before pixel data is decoded.

Mitigations: restrict accepted formats to the minimum required; enforce a file-size limit before passing data to Pillow; use per-request timeouts.

### Elevation of privilege

E-1 — C extension memory corruption (RCE)

Pillow’s ~87 C source files and its bundled C libraries process attacker-controlled bytes. Historical CVEs include buffer overflows, integer overflows, and use-after-free vulnerabilities that allow arbitrary code execution.

Mitigations: keep Pillow and all C libraries up to date; compile with hardening flags (ASLR, stack canaries, PIE, `_FORTIFY_SOURCE=2`); run image processing in a sandboxed subprocess (seccomp-bpf, AppArmor, or a restricted container).

E-2 — Ghostscript exploitation via EPS (RCE)

Opening an EPS file invokes the system Ghostscript binary (`gs`) via `subprocess`. Ghostscript has a long history of sandbox-escape CVEs permitting arbitrary code execution from malicious PostScript.

Mitigations: block EPS files at the application input layer before passing files to Pillow; if EPS must be supported, run Ghostscript in a fully isolated sandbox with no network and no sensitive mounts. Pillow does not provide a stable public API for unregistering individual format plugins, so do not rely on mutating internal registries such as `Image.OPEN` as a security control.

E-3 — ImageMath.unsafe_eval() code injection

`unsafe_eval()` calls Python’s built-in `eval()` with only a minimal `__builtins__` restriction, which can be bypassed via introspection. Any user-controlled string passed to this function results in arbitrary code execution.

Mitigations: never pass user-controlled strings to `ImageMath.unsafe_eval()`; use `lambda_eval()` instead, which accepts a Python callable and never calls `eval`.

E-4 — Font path traversal via ImageFont

`ImageFont.truetype(font, size)` passes the filename to the FreeType C library. If font paths are constructed from user input without canonicalisation, an attacker may supply a path like `../../../../etc/passwd`.

Mitigations: never construct font paths from user input; if font selection must be user-driven, resolve names against an explicit allowlist of pre-validated absolute paths.

## Recommendations

The following mitigations are listed in priority order.

1. Sandbox image processing — run Pillow workers in a seccomp/AppArmor restricted subprocess, isolated from the main application process.

2. Block or sandbox EPS — reject EPS at the application boundary, or run Ghostscript in an isolated container.

3. Never use `ImageMath.unsafe_eval()` with user input — migrate all callers to `lambda_eval()`.

4. Keep all dependencies current — Pillow and its C library dependencies (including libjpeg, libpng, libtiff, libwebp, openjpeg, freetype, littlecms2, Ghostscript, and others). Subscribe to Pillow security advisories.

5. Enforce `MAX_IMAGE_PIXELS` — never set it to `None`; treat `Image.DecompressionBombWarning` as an error.

6. Allowlist image formats — restrict accepted formats when opening images, for example with `Image.open(..., formats=...)`, and isolate installs/environments if you need to minimise supported formats.

7. Strip metadata on output — never pass through EXIF/XMP/ICC from user uploads to publicly served images.

8. Sanitise all metadata returned by Pillow before using it downstream.

9. Pin dependencies with hash verification — use `pip install --require-hashes` and lockfiles.

10. Log and alert on `Image.DecompressionBombWarning`, `Image.DecompressionBombError`, `PIL.UnidentifiedImageError`, and all exceptions from `Image.open()`.

## Reporting a vulnerability

To report sensitive vulnerability information, report it privately on GitHub.

If you cannot use GitHub, use the Tidelift security contact. Tidelift will coordinate the fix and disclosure.

Do not report sensitive vulnerability information in public.

Additionally:

1. Please ensure that your issue is reproducable in main. We only support the latest version. The one exception – if your issue is exploitable in the latest public release, but not in main, and the pull request does not mention a security implication this may be an unknown security issue that was inadvertely fixed.

2. Demonstrating a memory overflow is enough. Please do not weaponize the reproducer to do remote code execution.

3. Please do not report unexpected Python exceptions as a DoS or a memory safety bug. An issue that raises a Python exception in a library is unlikely to be considered a security issue. This may or may not be an ordinary bug depending on the context.