Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐍 PyPI
Not in CISA KEV

GHSA-24p2-j2jr-386w psd-tools

Fix: psd-tools/psd-tools@6c0a78f

GHSA-24p2-j2jr-386w is a CWE-190 vulnerability in psd-tools. A fix is available for psd-tools — see the affected versions and patch details below.

psd-tools: Compression module has unguarded zlib decompression, missing dimension validation, and hardening gaps

Also known asCVE-2026-27809PYSEC-2026-2262
Published
Feb 26, 2026
Updated
Jul 13, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Sep 19, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

No confirmed exploitation observed yet

  • CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
  • A successful exploit gives an attacker total control of the affected component, not partial access.
  • CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.

Exploitation and automatability from CISA’s SSVC triage for GHSA-24p2-j2jr-386w.

EPSS Exploitation Probability

via FIRST.org ↗
0.4%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs35th percentile — riskier than 35% of all scored CVEsHighest risk

EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.

Real-World Exposure

1 pkg affected
🐍psd-tools

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.

Description

Summary

A security review of the psd_tools.compression module (conducted against the fix/invalid-rle-compression branch, commits 7490ffa2a006f5) identified the following pre-existing issues. The two findings introduced and fixed by those commits (Cython buffer overflow, IndexError on lone repeat header) are excluded from this report.


Findings

1. Unguarded zlib.decompress — ZIP bomb / memory exhaustion (Medium)

Location: src/psd_tools/compression/__init__.py, lines 159 and 162

result = zlib.decompress(data)          # Compression.ZIP
decompressed = zlib.decompress(data)    # Compression.ZIP_WITH_PREDICTION

zlib.decompress is called without a max_length cap. A crafted PSD file containing a ZIP-compressed channel whose compressed payload expands to gigabytes would exhaust process memory before any limit is enforced. The RLE path is not vulnerable to this because the decoder pre-allocates exactly row_size × height bytes; the ZIP path has no equivalent ceiling.

Impact: Denial-of-service / OOM crash when processing untrusted PSD files.

Suggested mitigation: Pass a reasonable max_length to zlib.decompress, derived from the expected width * height * depth // 8 byte count already computed in decompress().


2. No upper-bound validation on image dimensions before allocation (Low)

Location: src/psd_tools/compression/__init__.py, lines 138 and 193

length = width * height * max(1, depth // 8)   # decompress()
row_size = max(width * depth // 8, 1)           # decode_rle()

Neither width, height, nor depth are range-checked before these values drive memory allocation. The PSD format (version 2 / PSB) permits dimensions up to 300,000 × 300,000 pixels; a 4-channel 32-bit image at that size would require ~144 TB to hold. While the OS/Python allocator will reject such a request, there is no early, explicit guard that produces a clean, user-facing error.

Impact: Uncontrolled allocation attempt from a malformed or adversarially crafted PSB file; hard crash rather than a recoverable error.

Suggested mitigation: Validate width, height, and depth against known PSD/PSB limits before entering decompression, and raise a descriptive ValueError early.


3. assert used as a runtime integrity check (Low)

Location: src/psd_tools/compression/__init__.py, line 170

assert len(result) == length, "len=%d, expected=%d" % (len(result), length)

This assertion can be silently disabled by running the interpreter with -O (or -OO), which strips all assert statements. If the assertion ever becomes relevant (e.g., after future refactoring), disabling it would allow a length mismatch to propagate silently into downstream image compositing.

Impact: Loss of an integrity guard in optimised deployments.

Suggested mitigation: Replace with an explicit if + raise ValueError(...).


4. cdef int indices vs. Py_ssize_t size type mismatch in Cython decoder (Low)

Location: src/psd_tools/compression/_rle.pyx, lines 18–20

cdef int i = 0
cdef int j = 0
cdef int length = data.shape[0]

All loop indices are C signed int (32-bit). The size parameter is Py_ssize_t (64-bit on modern platforms). The comparison j < size promotes j to Py_ssize_t, but if j wraps due to a row size exceeding INT_MAX (~2.1 GB), the resulting comparison is undefined behaviour in C. In practice, row sizes are bounded by PSD/PSB dimension limits and are unreachable at this scale; however, the mismatch is a latent defect if the function is ever called directly with large synthetic inputs.

Impact: Theoretical infinite loop or UB at >2 GB row sizes; not reachable from standard PSD/PSB parsing.

Suggested mitigation: Change cdef int i, j, length to cdef Py_ssize_t.


5. Silent data degradation not surfaced to callers (Informational)

Location: src/psd_tools/compression/__init__.py, lines 144–157

The tolerant RLE decoder (introduced in 2a006f5) replaces malformed channel data with zero-padded (black) pixels and emits a logger.warning. This is the correct trade-off over crashing, but the warning is only observable if the caller has configured a log handler. The public PSDImage API does not surface channel-level decode failures to the user in any other way.

Impact: A user parsing a silently corrupt file gets a visually wrong image with no programmatic signal to check.

Suggested mitigation: Consider exposing a per-channel decode-error flag or raising a distinct warning category that users can filter or escalate via the warnings module.


6. encode() zero-length return type inconsistency in Cython (Informational)

Location: src/psd_tools/compression/_rle.pyx, lines 66–67

if length == 0:
    return data   # returns a memoryview, not an explicit std::string

All other return paths return an explicit cdef string result. This path returns data (a const unsigned char[:] memoryview) and relies on Cython's implicit coercion to bytes. It is functionally equivalent today but is semantically inconsistent and fragile if Cython's coercion rules change in a future version.

Impact: Potential silent breakage in future Cython versions; not a current security issue.

Suggested mitigation: Replace return data with return result (the already-declared empty string).


Environment

  • Branch: fix/invalid-rle-compression
  • Reviewed commits: 7490ffa, 2a006f5
  • Python: 3.x (Cython extension compiled for CPython)

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐍PyPIpsd-toolsall versions1.12.2pip install --upgrade 'psd-tools==1.12.2'

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for psd-tools, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update psd-tools to 1.12.2 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-24p2-j2jr-386w is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like GHSA-24p2-j2jr-386w can be triaged on real exposure rather than presence alone.

Tailored to GHSA-24p2-j2jr-386w. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

## Summary A security review of the `psd_tools.compression` module (conducted against the `fix/invalid-rle-compression` branch, commits `7490ffa`–`2a006f5`) identified the following pre-existing issues. The two findings introduced and **fixed** by those commits (Cython buffer overflow, `IndexError` on lone repeat header) are excluded from this report. --- ## Findings ### 1. Unguarded `zlib.decompress` — ZIP bomb / memory exhaustion (Medium) **Location**: `src/psd_tools/compression/__init__.py`, lines 159 and 162 ```python result = zlib.decompress(data) # Compression.ZIP decompre
O3 Security · Impact-Aware SCA

Is GHSA-24p2-j2jr-386w in your dependencies?

O3 Security finds GHSA-24p2-j2jr-386w across PyPI dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.

GHSA-24p2-j2jr-386w: psd-tools | O3 Security