### Summary
On Windows, attacker-controlled content in a wheel's
`.dist-info/namespace_packages.txt` can cause setuptools' legacy wheel
installation path to create an `__init__.py` outside the requested
`destination_eggdir`.
The attacker can influence the destination path, but not the contents of
the created file.
### Details
Tested setuptools commit:
`6ead555c5fb29bc57fe6105b1bffc163f56fd558`
Relevant source:
`setuptools/wheel.py`
SHA-256:
`359b7e0d5dd8c3135653abe08a8ed5dc0352eadfd7df22cb5e2b5303b7c3cafd`
The relevant call path is:
`Wheel(wheel_path).install_as_egg(destination)`
→ `_install_as_egg`
→ `_convert_metadata`
→ `_fix_namespace_packages`
A wheel can provide attacker-controlled content in:
`<distribution>.dist-info/namespace_packages.txt`
The namespace value is eventually used approximately as:
```python
mod_dir = os.path.join(destination_eggdir, *mod.split('.'))
mod_init = os.path.join(mod_dir, '__init__.py')
If mod is a Windows drive-qualified absolute path containing no dots,
mod.split('.') preserves it as a single component.
On Windows, os.path.join(destination_eggdir, absolute_component) discards
the original base directory and resolves to the attacker-supplied absolute
path.
The existing wheel filename and ZIP traversal checks do not validate this
metadata value before it is subsequently interpreted as a filesystem path.
```markdown
### PoC
The reproduction was performed entirely inside a disposable temporary
directory.
Layout:
```text
sandbox/
destination/
outside/
input/
canary.txt
```
A synthetic wheel was created containing:
`demo_pkg-1.0.dist-info/namespace_packages.txt`
whose content was the absolute path of:
`sandbox\outside`
The real acquired setuptools implementation was then invoked:
```python
setuptools.wheel.Wheel(synthetic_wheel).install_as_egg(
sandbox_destination
)
```
The operation completed without exception.
Expected metadata was created under:
`sandbox\destination\EGG-INFO`
but setuptools also created:
`sandbox\outside\__init__.py`
The file did not exist before execution.
Its contents were:
```python
__import__('pkg_resources').declare_namespace(__name__)
```
The unrelated canary remained byte-identical.
The sandbox was deleted after recording the evidence.
```
### Impact
A crafted wheel can cause setuptools to create a fixed-content
`__init__.py` outside the intended installation directory at a
filesystem path influenced by the wheel, with the permissions of the
process performing the installation.
The demonstrated reproduction required the target parent directory to
already exist.
### Impact
The msgpack-python C extension (`msgpack._cmsgpack`) leaks a decoded map value when deserialization rejects a key with `strict_map_key=True` (the default). Repeated malformed inputs can cause unbounded process memory growth and denial of service in applications that deserialize attacker-controlled MessagePack data. Both `unpackb()` and the streaming `Unpacker` API are affected. The pure-Python fallback does not exhibit this leak in the reported tests.
The report measured approximately 4 MiB retained per failed parse and 100 MiB after 25 failed parses on Windows with Python 3.14 and msgpack 1.2.2. These are reporter-provided results.
### Additional impact: stale parser-stack references
The reference-ownership audit performed while fixing this leak found an
additional memory-safety issue in the streaming `Unpacker`.
After `unpack()` completed a nested container, an inactive parser-stack slot
could retain a non-owning pointer to the decoded object. A subsequent `skip()`
could reactivate that slot. If parsing then failed, context cleanup treated the
stale pointer as an owned reference and decremented it.
This corrupts the object's reference count and can result in a use-after-free
or double `Py_DECREF` when its actual owners later release it.
Although few users use this API, but those who do may experience crashes or unexpected errors due to a use-after-free.
### Details
In `msgpack/unpack.h`, `unpack_callback_map_item()` rejects keys that are neither `str` nor `bytes`. At this point the map value has already been constructed. The early error return does not release the value reference.
In `msgpack/unpack_template.h`, the value is held in the local `obj` in `unpack_execute()`. The `CT_MAP_VALUE` error path jumps to `_failed` without decrementing that reference. Cleanup of the parser stack does not recover the value because it is not stored in the stack. The fallback checks the key before decoding the value.
### Affected versions
The report reproduces the issue on **1.2.2** and reports it on development commit `475c1b7`. The affected-product field lists the reproduced release only; it is not a claim that earlier releases are unaffected.
The earliest affected release needs confirmation: the report's summary says 1.1.x through 1.2.2, while its root-cause analysis attributes introduction to commit `cd81370` (PR #673). Resolve this discrepancy before publishing a definitive affected-version range.
### Reproduction
Run in a disposable local process with the C extension installed:
```python
import msgpack
payload = msgpack.packb({42: b"A" * (4 * 1024 * 1024)}, use_bin_type=True)
msgpack.unpackb(payload) # Raises ValueError; the report observes a 4 MiB leak.
```
Streaming API trigger:
```python
import msgpack
payload = msgpack.packb({42: b"A" * (4 * 1024 * 1024)}, use_bin_type=True)
u = msgpack.Unpacker()
u.feed(payload)
list(u) # Same strict_map_key error path.
```
### Workarounds
The pure-Python fallback is reported not to leak on this path and may be used as a temporary workaround after application compatibility and performance checks. Request-size and rate limits may reduce the impact but do not eliminate cumulative leakage.
### Impact
A maliciously crafted DNS message may cause dns.name.from_wire_parser() to use excessive CPU for DNS name decompression. Applications that process untrusted DNS wire form messages with dnspython may be degraded or DoS'd by the extra CPU use required.
### Patches
This problem is fixed in dnspython 2.9.0.
### Workarounds
There are no known workarounds.
### Credit
Evgenios Gkritsis, Department of Informatics, University of Piraeus & Athena Research Center, Greece
Constantinos Patsakis, Department of Informatics, University of Piraeus & Athena Research Center, Greece
George Stergiopoulos, Department of Informatics, Athens University of Economics and Business, Greece
### Summary
Multiple cases were found where aiohttp could use excessive or unbounded memory, or excessive CPU consumption on attacker controlled requests.
### Impact
An attacker could cause resource exhaustion through specially crafted requests.
### Summary
The pure Python parser is vulnerable to an HTTP request smuggling attack.
### Impact
If users are using the Python parser (`AIOHTTP_NO_EXTENSIONS`, or not a standard wheel installed), then an application may be vulnerable to a request smuggling attack.
### Workaround
Switch to the default C parser.
### Summary
A subdomain can remove the Host-Only flag from an existing cookie to a parent domain.
### Impact
A user accessing untrusted subdomains could see their Host-Only protections stripped from cookies. Given most usecases won't reflect these cookies to a browser, the impact is limited.
### Summary
An attacker sending an absolute-form request can spoof the scheme used.
### Impact
If user code depends on the `Request.scheme` attribute for anything security related, an attacker may be able to circumvent this logic, tricking the application into thinking a connection is encrypted when it is not, or vice versa.
### Workaround
A WAF rule to reject absolute-form requests (legitimate requests should only use this form for proxies).
### Summary
An attacker can exhaust file descriptors with specially crafted multipart requests.
### Impact
If a handler uses `Request.post()`, it may be possible for an attacker to use an unlimited number of file descriptors, potentially resulting in resource exhaustion.
### Summary
An overflow in Max-Age processing can skip closing a connection.
### Impact
An attacker may be able to exhaust the connection pool temporarily, until garbage collection resolves the connections.
### Summary
The keepalive timeout was not used during the initial portion of a request.
### Impact
An attacker could keep an idle connection open indefinitely before sending any data.
### Workaround
Use a reverse proxy (as recommended for all production deployments) with sensible keepalive limits.
### Summary
The Secure attribute was not being enforced for the shared cookie.
### Impact
It may be possible to send a shared cookie marked Secure through an unencrypted request.
## Description
A reference leak in the items-view union and subtraction operators of aio-libs/multidict 6.7.0 through 6.9.0 (C extension) lets a remote client drive unbounded, unreclaimable memory growth by having each operand element leak one key-identity object and one value object. The reflected-union path (`operand | d.items()`, `multidict_itemsview_or2_impl`) and the subtraction path (`d.items() - operand`, `multidict_itemsview_sub1_impl`) parse each element into new strong references but release only the tuple wrapper, never the identity and value. Servers in the aio-libs stack build these views over attacker-supplied HTTP headers and query strings, so the operand size is under remote control. Forced garbage collection does not recover the leaked objects, so resident memory rises monotonically until the process is killed.
---
## Root Cause
`_multidict_itemsview_parse_item()` returns **new** references: a fresh identity via `md_calc_identity()` and a fresh value via `Py_NewRef()`. The `or2_impl` first parse loop requests both but clears only `arg`:
```c
// views.h:565 — or2_impl first parse loop
while ((st = PyIter_NextItem(iter, &arg)) > 0) {
int tmp = _multidict_itemsview_parse_item(
self, arg, &identity, NULL, &value); // identity + value: new refs
if (tmp < 0) goto fail;
else if (tmp > 0) {
if (_set_add(tmp_set, identity, value) < 0) goto fail;
}
Py_CLEAR(arg); // :575 clears arg ONLY
}
```
`_set_add()` builds its own tuple with `PyTuple_Pack` and `Py_DECREF`s it, so it never borrows the loop's `identity`/`value`. On the next iteration those variables are overwritten, so the previous references are lost permanently. The `sub1_impl` first loop (`:686-696`) has the identical omission.
This is an editing slip, not an ownership contract: every sibling path clears all three references per iteration, `and1_impl` (`:304-306`), `and2_impl` (`:389-391`), `or1_impl` (`:506-508`), and the second (`md_next`) loops of both functions (`:605-607`, `:726-728`). PR #1413 added a `Py_DECREF(tpl)` to the *second* loop of `or2`/`sub1`, fixing a different temporary-tuple leak; it never touched the first parse loop, so this leak remains on `master`.
`views.h` is byte-identical between `v6.6.0` and `v6.7.0`, yet 6.6.4 does not leak and 6.7.0 does. The regression is behavioural, introduced by a key-identity ownership change in `hashtable.h` across that boundary that made `md_calc_identity()` return a fresh strong reference the unchanged parse loop never releases.
---
## Reproduction Environment
| Item | Value |
|------|-------|
| **Runtime** | CPython 3.14.6 |
| **multidict** | 6.9.0 (PyPI binary wheel, C extension) |
| **OS** | macOS (darwin 25.6.0, arm64) |
---
## Proof of Concept
### POC Source Code
#### poc_refcount.py — leak proof with intersection control
```python
import sys, gc
from multidict import CIMultiDict
def probe(name, run_op):
d = CIMultiDict(); d["seed"] = "x"
value = object() # unique sentinel
N = 100000
operand = [("k%d" % i, value) for i in range(N)]
gc.collect(); before = sys.getrefcount(value)
run_op(d, operand)
gc.collect(); after = sys.getrefcount(value)
print(f"[{name}] leaked strong refs = {after - before}")
probe("or2 operand | items", lambda d, o: o | d.items()) # reflected union
probe("sub1 items - operand", lambda d, o: d.items() - o) # subtraction
probe("and2 items & operand", lambda d, o: d.items() & o) # CONTROL: clears, expect 0
```
#### poc_dispatch.py — maps which paths leak
```python
import sys, gc
from multidict import CIMultiDict
def probe(name, fn):
d = CIMultiDict(); d["seed"] = "x"
v = object(); N = 50000
operand = [("k%d" % i, v) for i in range(N)]
gc.collect(); before = sys.getrefcount(v)
fn(d, operand); gc.collect(); after = sys.getrefcount(v)
print(f"{name:35s} leaked={after - before}")
probe("operand | d.items() (or2)", lambda d, o: o | d.items())
probe("d.items() | operand (or1)", lambda d, o: d.items() | o)
probe("d.items() - operand (sub1)", lambda d, o: d.items() - o)
probe("operand - d.items() (rsub)", lambda d, o: set(o) - d.items())
probe("d.items() & operand (and)", lambda d, o: d.items() & o)
```
#### poc_rss.py — availability consequence
```python
import sys, gc, resource
from multidict import CIMultiDict
def rss_mb():
r = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
return r / (1024 * 1024) if sys.platform == "darwin" else r / 1024
d = CIMultiDict(); d["seed"] = "x"
gc.collect(); print(f"start RSS = {rss_mb():.1f} MB")
for i in range(200):
operand = [(f"k{i}_{j}", f"v{i}_{j}") for j in range(50000)]
_ = operand | d.items()
del operand
gc.collect() # prove GC cannot reclaim the leak
if (i + 1) % 40 == 0:
print(f"after {(i+1)*50000:>9,} elements: RSS = {rss_mb():.1f} MB")
```
### Execution Steps
1. `python3 -m venv venv` (CP
## 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`, `_find_all_simple`), 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/egg_info.py (FileList.global_exclude)
def global_exclude(self, pattern):
match = translate_pattern(os.path.join('**', pattern)) # fnmatch.translate -> regex, no NFC/NFD
return self._remove_files(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/unicode_utils.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._manifest_normalize`, uses `filesys_decode` (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 secret_café.txt # rule saved NFC
```
and an on-disk file `secret_café.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/egg_info.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.
### Summary
A path traversal vulnerability in `PackageIndex` was fixed in setuptools version 78.1.1
### Details
```
def _download_url(self, url, tmpdir):
# Determine download filename
#
name, _fragment = egg_info_for_url(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](http://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/package_index.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 easy_install and package_index 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
A vulnerability in the `package_index` module of pypa/setuptools versions up to 69.1.1 allows for remote code execution via its download functions. These functions, which are used to download packages from URLs provided by users or retrieved from package index servers, are susceptible to code injection. If these functions are exposed to user-controlled inputs, such as package URLs, they can execute arbitrary commands on the system. The issue is fixed in version 70.0.
A stored cross-site scripting (XSS) vulnerability exists in NetBox 4.1.0 within the "Configuration History" feature of the "Admin" panel via a /core/config-revisions/ Add action. An authenticated user can inject arbitrary JavaScript or HTML into the "Top banner" field. NOTE: Multiple third parties have disputed this as not a vulnerability. It is argued that the configuration revision banner feature is meant to contain unsanitized HTML in order to display notifications to users. Since these fields are intended to display unsanitized HTML, this is working as intended.
### Summary
llhttp 8.1.1 is vulnerable to two request smuggling vulnerabilities.
Details have not been disclosed yet, so refer to llhttp for future information.
The issue is resolved by using llhttp 9+ (which is included in aiohttp 3.8.6+).
### Summary
An out-of-bounds heap read could occur in the C response parser while building an error message for a malformed response.
### Impact
An attacker controlled server, or possibly an accidental response could trigger a DoS in the client.
### Workaround
If unable to upgrade, the Python parser is unaffected and can be used with `AIOHTTP_NO_EXTENSIONS=1`.
---
Patch: https://github.com/aio-libs/aiohttp/commit/49f65d54150397892f7bcc4aae887767d51c322d
### Summary
The HTTP parsers were vulnerable to a request smuggling attack relating to WebSocket upgrades.
### Impact
If using the server-side component, it may be possible for an attacker to execute a request smuggling vulnerability using an edge case in the WebSocket upgrade procedure. AIOHTT is unaware of any public exploit code.
---
Patch: https://github.com/aio-libs/aiohttp/commit/6ae358f0983c3f4d6f67692b2f8e65dc8e091c98