Skip to content

Commit 9f24881

Browse files
aclark4lifeCopilot
andcommitted
Add STRIDE threat model to security docs
- Update .github/SECURITY.md with threat model summary and link to handbook - Add docs/handbook/security.rst with full STRIDE analysis (14 threats across Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, and Elevation of Privilege categories) - Add prioritised mitigation recommendations - Link security.rst into the handbook toctree Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3a3dab8 commit 9f24881

3 files changed

Lines changed: 277 additions & 1 deletion

File tree

.github/SECURITY.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
11
# Security policy
22

3+
## Reporting a vulnerability
4+
35
To report sensitive vulnerability information, report it [privately on GitHub](https://github.com/python-pillow/Pillow/security/advisories/new).
46

57
If you cannot use GitHub, use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
68

7-
DO NOT report sensitive vulnerability information in public.
9+
**DO NOT report sensitive vulnerability information in public.**
10+
11+
## Threat model
12+
13+
Pillow's primary attack surface is parsing untrusted image data. A full STRIDE threat model covering spoofing, tampering, repudiation, information disclosure, denial of service, and elevation of privilege is maintained in the [Security handbook page](https://pillow.readthedocs.io/en/stable/handbook/security.html).
14+
15+
Key risks to be aware of when using Pillow to process untrusted images:
16+
17+
- **Decompression bombs** — do not set `Image.MAX_IMAGE_PIXELS = None` in production.
18+
- **EPS files invoke Ghostscript** — block EPS input at the application layer unless strictly required.
19+
- **`ImageMath.unsafe_eval()`** — never pass user-controlled strings to this function; use `lambda_eval` instead.
20+
- **C extension memory safety** — keep Pillow and its bundled C libraries (libjpeg, libpng, libtiff, libwebp, etc.) up to date.
21+
- **Sandboxing** — for high-risk deployments, run image processing in a sandboxed subprocess.

docs/handbook/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ Handbook
88
tutorial
99
concepts
1010
appendices
11+
security

docs/handbook/security.rst

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
Security
2+
========
3+
4+
Pillow's primary attack surface is **parsing untrusted image data**. This page
5+
documents the threat model for developers integrating Pillow into applications
6+
that handle images from untrusted sources, along with recommended mitigations.
7+
8+
To report a vulnerability see :ref:`security-reporting`.
9+
10+
.. _security-threat-model:
11+
12+
Threat model (STRIDE)
13+
---------------------
14+
15+
The analysis below follows the `STRIDE
16+
<https://en.wikipedia.org/wiki/STRIDE_(security)>`_ framework and covers the
17+
boundary between untrusted image input and the Pillow API.
18+
19+
.. code-block:: text
20+
21+
┌──────────────────────────────────────────┐
22+
Untrusted zone │ Pillow API │
23+
───────────── │ │
24+
Image files ────►│ Image.open() ──► Format plugins │
25+
Byte streams │ (40+ parsers) (Python + C FFI) │
26+
User metadata │ │
27+
│ ImageMath.unsafe_eval(expr) ───────────┼──► Python eval()
28+
│ ImageShow.show(image) ─────────────────┼──► os.system / subprocess
29+
│ EpsImagePlugin.open(eps) ──────────────┼──► Ghostscript (gs)
30+
└──────────────┬───────────────────────────┘
31+
│ C extension (_imaging)
32+
33+
┌──────────────────────────────────────────┐
34+
│ C libraries (bundled or system) │
35+
│ libjpeg · libpng · libtiff · libwebp │
36+
│ openjpeg · freetype · littlecms │
37+
└──────────────────────────────────────────┘
38+
39+
Spoofing
40+
^^^^^^^^
41+
42+
**S-1 — Format sniffing bypass**
43+
44+
``Image.open()`` detects format by magic bytes, not file extension or MIME
45+
type. An attacker can name a file ``safe.png`` while its content is TIFF, JPEG
46+
2000, or EPS, causing a different — potentially more dangerous — parser to run.
47+
48+
*Mitigations:* validate MIME type and magic bytes independently before calling
49+
``Image.open()``; pass the ``format`` parameter explicitly; maintain an
50+
allowlist of accepted formats.
51+
52+
**S-2 — Plugin registry spoofing**
53+
54+
Pillow's format registry is a global mutable dictionary. A malicious package
55+
installed in the same environment could register a replacement parser for a
56+
well-known format.
57+
58+
*Mitigations:* use isolated virtual environments with pinned, hash-verified
59+
dependencies; audit ``Image.registered_extensions()`` at startup.
60+
61+
Tampering
62+
^^^^^^^^^
63+
64+
**T-1 — Malicious metadata propagation**
65+
66+
Pillow preserves EXIF, XMP, IPTC, ICC profiles, and comments when
67+
round-tripping images. Applications that store or render metadata without
68+
sanitisation are vulnerable to second-order injection (SQLi, XSS, command
69+
injection).
70+
71+
*Mitigations:* treat all values from ``image.info``, ``image._getexif()``, and
72+
``image.text`` as untrusted; sanitise before storing or rendering; strip
73+
metadata when it is not required.
74+
75+
**T-2 — Covert data channel (steganography)**
76+
77+
Pillow does not remove hidden data (JPEG comments, PNG text chunks, appended
78+
bytes) when re-saving. An attacker can embed data that survives the
79+
encode-decode cycle invisibly.
80+
81+
*Mitigations:* to guarantee a clean output, load pixel data via
82+
``image.tobytes()`` and rebuild the image from raw bytes before saving.
83+
84+
**T-3 — Supply chain tampering**
85+
86+
Pre-compiled wheels bundle libjpeg-turbo, libpng, libtiff, libwebp, openjpeg,
87+
freetype, and littlecms. A compromised PyPI release or build pipeline could
88+
ship malicious binaries.
89+
90+
*Mitigations:* pin with hash verification (``pip install --require-hashes``);
91+
monitor `Pillow security advisories
92+
<https://github.com/python-pillow/Pillow/security/advisories>`_; use
93+
Dependabot or OSV-Scanner for bundled C library CVEs.
94+
95+
Repudiation
96+
^^^^^^^^^^^
97+
98+
**R-1 — No structured audit trail**
99+
100+
Pillow does not emit structured audit logs of files opened, formats detected,
101+
or operations performed, making forensic investigation harder after an
102+
incident.
103+
104+
*Mitigations:* applications should log the filename/hash, detected format, and
105+
dimensions of every image processed; log and alert on
106+
``Image.DecompressionBombWarning`` and ``PIL.UnidentifiedImageError``.
107+
108+
Information disclosure
109+
^^^^^^^^^^^^^^^^^^^^^^
110+
111+
**I-1 — Metadata in saved images**
112+
113+
GPS coordinates, author names, software version strings, and ICC profiles can
114+
be inadvertently included in output images served publicly.
115+
116+
*Mitigations:* explicitly strip EXIF and XMP on save (set ``exif=b""``,
117+
``icc_profile=None``, omit ``pnginfo``); verify output with ``exiftool`` in CI.
118+
119+
**I-2 — Sensitive exception messages**
120+
121+
Parser errors can include byte offsets, dimension values, and tile descriptors.
122+
Propagating these to API responses aids attacker reconnaissance.
123+
124+
*Mitigations:* catch ``PIL.UnidentifiedImageError``,
125+
``PIL.Image.DecompressionBombError``, and general exceptions at the
126+
application boundary; return generic messages to clients.
127+
128+
**I-3 — Temporary file exposure**
129+
130+
Several code paths write pixel data to temporary files via
131+
``tempfile.mkstemp()``. Exception paths can leave these files behind on shared
132+
filesystems.
133+
134+
*Mitigations:* files are created with mode ``0o600``; mount ``/tmp`` as a
135+
per-container ``tmpfs``; ensure ``try/finally`` cleanup is in place.
136+
137+
Denial of service
138+
^^^^^^^^^^^^^^^^^
139+
140+
**D-1 — Decompression bomb**
141+
142+
A small compressed image can expand to gigabytes in memory.
143+
:py:data:`PIL.Image.MAX_IMAGE_PIXELS` (~89 MP by default) raises
144+
``DecompressionBombError`` at 2× the limit and
145+
``DecompressionBombWarning`` at 1×. PNG text chunks are
146+
separately capped by ``PngImagePlugin.MAX_TEXT_CHUNK`` (1 MiB) and
147+
``MAX_TEXT_MEMORY`` (64 MiB).
148+
149+
*Mitigations:* **never** set ``Image.MAX_IMAGE_PIXELS = None`` in production;
150+
treat ``DecompressionBombWarning`` as an error; set OS/container memory limits
151+
per worker.
152+
153+
**D-2 — CPU exhaustion**
154+
155+
Large-but-legal images (within ``MAX_IMAGE_PIXELS``) can still saturate CPU
156+
through high-quality resampling, convolution filters, or complex draw
157+
operations.
158+
159+
*Mitigations:* apply per-request CPU time limits; set a practical dimension
160+
ceiling below ``MAX_IMAGE_PIXELS``; rate-limit processing requests.
161+
162+
**D-3 — Algorithmic complexity in parsers**
163+
164+
Formats such as TIFF (nested IFD chains), animated GIF/WebP (many frames), and
165+
PNG (many text chunks) can exhaust CPU or memory before pixel data is decoded.
166+
167+
*Mitigations:* restrict accepted formats to the minimum required; enforce a
168+
file-size limit before passing data to Pillow; use per-request timeouts.
169+
170+
Elevation of privilege
171+
^^^^^^^^^^^^^^^^^^^^^^
172+
173+
**E-1 — C extension memory corruption (RCE)**
174+
175+
Pillow's ~87 C source files and its bundled C libraries process
176+
attacker-controlled bytes. Historical CVEs include buffer overflows, integer
177+
overflows, and use-after-free vulnerabilities that allow arbitrary code
178+
execution.
179+
180+
*Mitigations:* keep Pillow and all C libraries up to date; compile with
181+
hardening flags (ASLR, stack canaries, PIE, ``_FORTIFY_SOURCE=2``); run image
182+
processing in a sandboxed subprocess (seccomp-bpf, AppArmor, or a restricted
183+
container).
184+
185+
**E-2 — Ghostscript exploitation via EPS (RCE)**
186+
187+
Opening an EPS file invokes the system Ghostscript binary (``gs``) via
188+
``subprocess``. Ghostscript has a long history of sandbox-escape CVEs
189+
permitting arbitrary code execution from malicious PostScript.
190+
191+
*Mitigations:* **block EPS files** at the application input layer; if EPS must
192+
be supported, run Ghostscript in a fully isolated sandbox with no network and
193+
no sensitive mounts; unregister the plugin if unused::
194+
195+
from PIL import Image, EpsImagePlugin
196+
Image.OPEN.pop("EPS", None)
197+
198+
**E-3 — ``ImageMath.unsafe_eval()`` code injection**
199+
200+
:py:meth:`~PIL.ImageMath.unsafe_eval` calls Python's built-in ``eval()`` with
201+
only a minimal ``__builtins__`` restriction, which can be bypassed via
202+
introspection. Any user-controlled string passed to this function results in
203+
arbitrary code execution.
204+
205+
*Mitigations:* **never** pass user-controlled strings to
206+
``ImageMath.unsafe_eval()``; use :py:meth:`~PIL.ImageMath.lambda_eval` instead,
207+
which accepts a Python callable and never calls ``eval``.
208+
209+
**E-4 — Font path traversal via ``ImageFont``**
210+
211+
``ImageFont.truetype(font, size)`` passes the filename to the FreeType C
212+
library. If font paths are constructed from user input without
213+
canonicalisation, an attacker may supply a path like
214+
``../../../../etc/passwd``.
215+
216+
*Mitigations:* never construct font paths from user input; if font selection
217+
must be user-driven, resolve names against an explicit allowlist of
218+
pre-validated absolute paths.
219+
220+
.. _security-recommendations:
221+
222+
Recommendations
223+
---------------
224+
225+
The following mitigations are listed in priority order.
226+
227+
1. **Sandbox image processing** — run Pillow workers in a seccomp/AppArmor-
228+
restricted subprocess, isolated from the main application process.
229+
2. **Block or sandbox EPS** — reject EPS at the application boundary, or run
230+
Ghostscript in an isolated container.
231+
3. **Never use** ``ImageMath.unsafe_eval()`` **with user input** — migrate all
232+
callers to :py:meth:`~PIL.ImageMath.lambda_eval`.
233+
4. **Keep all dependencies current** — Pillow, libjpeg, libpng, libtiff,
234+
libwebp, openjpeg, freetype, Ghostscript. Subscribe to `Pillow security
235+
advisories <https://github.com/python-pillow/Pillow/security/advisories>`_.
236+
5. **Enforce** ``MAX_IMAGE_PIXELS`` — never set it to ``None``; treat
237+
``DecompressionBombWarning`` as an error.
238+
6. **Allowlist image formats** — unregister plugins your application does not
239+
need.
240+
7. **Strip metadata on output** — never pass through EXIF/XMP/ICC from user
241+
uploads to publicly served images.
242+
8. **Sanitise all metadata** returned by Pillow before using it downstream.
243+
9. **Pin dependencies with hash verification** — use
244+
``pip install --require-hashes`` and lockfiles.
245+
10. **Log and alert** on ``DecompressionBombWarning``,
246+
``DecompressionBombError``, ``PIL.UnidentifiedImageError``,
247+
and all exceptions from ``Image.open()``.
248+
249+
.. _security-reporting:
250+
251+
Reporting a vulnerability
252+
-------------------------
253+
254+
To report sensitive vulnerability information, report it `privately on GitHub
255+
<https://github.com/python-pillow/Pillow/security/advisories/new>`_.
256+
257+
If you cannot use GitHub, use the `Tidelift security contact
258+
<https://tidelift.com/security>`_. Tidelift will coordinate the fix and
259+
disclosure.
260+
261+
**Do not report sensitive vulnerability information in public.**

0 commit comments

Comments
 (0)