Skip to main content
Vulnerability Database/CVE-2026-59991

CVE-2026-59991: psd-tools Python Package DOS Vulnerability

CVE-2026-59991 is a denial of service vulnerability in psd-tools that allows attackers to cause memory exhaustion through crafted PSD files. This post explains its technical details, affected versions, and mitigation steps.

Published:

CVE-2026-59991 Overview

CVE-2026-59991 is an uncontrolled memory allocation vulnerability in psd-tools, a Python package for reading and writing Adobe Photoshop PSD files. Versions prior to 1.17.4 allocate output buffers from attacker-controlled PSD header geometry, including width, height, channels, depth, and per-layer rectangles, before validating those values against the file data. A small crafted PSD file can trigger multi-gigabyte memory allocations inside PSDImage.composite() and PSDImage.numpy(). Services that composite untrusted PSD files can be terminated by out-of-memory (OOM) handling. The issue is fixed in psd-tools version 1.17.4 and is tracked under [CWE-789: Memory Allocation with Excessive Size Value].

Critical Impact

A tiny crafted PSD file can force multi-gigabyte memory allocations, causing denial of service in any service that composites untrusted PSD input.

Affected Products

  • psd-tools Python package versions prior to 1.17.4
  • Web services and pipelines that render or convert user-supplied PSD files
  • Automation and preview tooling that invokes PSDImage.composite() or PSDImage.numpy()

Discovery Timeline

  • 2026-09-22 - CVE-2026-59991 published to NVD
  • 2026-09-23 - Last updated in NVD database

Technical Details for CVE-2026-59991

Vulnerability Analysis

The defect lives in the image composition and NumPy conversion paths of psd-tools. Both PSDImage.composite() and PSDImage.numpy() size their output buffers using values pulled directly from the PSD header and per-layer rectangle metadata. These values include width, height, channel count, bit depth, and layer bounding boxes. The library allocates the buffers before confirming that the referenced pixel data actually exists in the file. An attacker who supplies a small PSD with inflated geometry fields can force allocations far larger than the file itself. In addition, PSDImage.composite() can return a black image with only a warning rather than raising an exception, which masks the failure from calling code. The result is memory exhaustion and process termination on any service that composites untrusted PSD input.

Root Cause

The root cause is missing validation of untrusted size values before allocation, matching [CWE-789]. The helper check_pixel_size() previously validated only width and height, ignoring the channels dimension used to shape the output array. Header geometry was treated as authoritative rather than as adversary-controlled input.

Attack Vector

Exploitation requires no authentication and no user interaction beyond submitting a PSD file to a vulnerable service. Any network-reachable endpoint that accepts PSD uploads and calls the affected APIs is exposed.

python
# Patch excerpt: src/psd_tools/api/numpy_io.py
def get_image_data(psdimage: "PSDProtocol", channel: str | None) -> np.ndarray:
-    check_pixel_size(psdimage.width, psdimage.height)
+    check_pixel_size(psdimage.width, psdimage.height, psdimage.channels)

    if (channel == "mask") or (channel == "shape" and not has_transparency(psdimage)):
        return np.ones((psdimage.height, psdimage.width, 1), dtype=np.float32)

# Patch excerpt: src/psd_tools/api/pil_io.py
-    check_pixel_size(psd.width, psd.height)
+    check_pixel_size(psd.width, psd.height, psd.channels)

    if channel is not None and channel >= psd.channels:
        raise ValueError(
# Source: https://github.com/psd-tools/psd-tools/commit/a3d9a53ad51e667b5772a4f636ca6f2e16f4b271

The patch extends check_pixel_size() to include the channel count, bounding the allocation footprint against undecodable channels.

Detection Methods for CVE-2026-59991

Indicators of Compromise

  • Python worker processes terminated by the Linux OOM killer shortly after receiving a PSD upload
  • Warnings from psd-tools reporting a returned black image without an accompanying decode success
  • PSD files whose declared width, height, or channel product implies allocations far larger than the file size on disk

Detection Strategies

  • Inspect PSD headers before processing and reject files where width * height * channels * (depth/8) exceeds a safe upper bound
  • Log the resident set size (RSS) of PSD worker processes and alert on sudden multi-gigabyte growth
  • Correlate application logs that show PSDImage.composite() warnings with process restarts or 5xx responses

Monitoring Recommendations

  • Track memory pressure and OOM events on hosts running services that ingest user-supplied PSD files
  • Monitor the installed version of psd-tools across build artifacts and container images using a software bill of materials (SBOM)
  • Alert on repeated PSD upload failures from a single source, which may indicate probing for the DoS condition

How to Mitigate CVE-2026-59991

Immediate Actions Required

  • Upgrade psd-tools to version 1.17.4 or later across all services, workers, and container images
  • Enforce memory and CPU limits on processes that handle untrusted PSD files to contain OOM impact
  • Validate PSD header geometry at the application layer before invoking PSDImage.composite() or PSDImage.numpy()

Patch Information

The fix is available in GitHub Release v1.17.4 and is detailed in GitHub Security Advisory GHSA-8q6g-vjhf-jp8m. The code change is available in the upstream commit, which extends check_pixel_size() to include the channel count.

Workarounds

  • Reject PSD uploads that exceed a maximum declared pixel count or channel depth before parsing
  • Run PSD processing inside a sandbox or container with strict cgroup memory limits
  • Treat the black-image warning path in PSDImage.composite() as a hard error and fail the request
bash
# Pin the fixed version in requirements.txt
psd-tools>=1.17.4

# Or upgrade directly
pip install --upgrade 'psd-tools>=1.17.4'

# Example systemd hardening for a PSD worker service
# /etc/systemd/system/psd-worker.service.d/limits.conf
[Service]
MemoryMax=1G
MemoryHigh=768M
TasksMax=64

Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

Experience the Most Advanced Cybersecurity Platform

See how the world’s most intelligent, autonomous cybersecurity platform can protect your organization today and into the future.