TUF · CVE-2026-123806 · Package
FIX → 7.0.0
On Windows, python-tuf's ngclient matches delegated target paths case-insensitively, while Linux and macOS match case-sensitively, so the same repository can authorize different targets depending on the client's operating system. An attacker who controls a delegated role with a case-differing path pattern placed earlier in the delegation order can get a target accepted on Windows clients.
first public May 28, 2026 · 1 source
Could this affect your app?
Free first check from your website. Connect your repo for the full answer.
Teams that run python-tuf ngclient on Windows hosts to verify TUF repositories with path-based delegations. Exposure needs a repository whose delegation patterns differ only in case and an attacker-controlled delegated role visited first. Linux and macOS clients are not affected, and repositories without path-based delegations can skip it.
Not affected if linux-or-macos-only, no-path-delegations, tuf-7.0.0-or-later
Who cares ngclient, python, tuf, windows
tuf version with pip show tuf and compare it to the fixed range.ngclient to update trusted metadata or targets.Upgrade to 7.0.0
DelegatedRole._is_target_in_pathpattern uses fnmatch.fnmatch to decide whether a given target path is authorized by a delegation's glob pattern.
Python's fnmatch.fnmatch calls os.path.normcase() on both arguments before matching. On POSIX hosts normcase is the identity function; on Windows hosts os.path resolves to ntpath, whose normcase lowercases its input and replaces / with \.
As a result, python-tuf's delegation path pattern matching is case-sensitive on Linux/macOS but case-INSENSITIVE on Windows. This makes the authorization decision for a target dependent on the host operating system of the client running the updater.
The result on Windows is a TUF specification violation in the python-tuf ngclient implementation.
Check it with your coding agent
It reads this page and tells you if your code is affected.
Are you affected?
Ask from here. InfraStack checks the affected versions against your stack and answers in your inbox.
InfraStack checks the affected versions against your stack and answers in Slack, with the evidence and the fix.
tuf7.0.06.0.0fnmatch.fnmatchfnmatch.fnmatchcasetuf/api/_payload.py (HEAD 7ecb67d):
1183 @staticmethod
1184 def _is_target_in_pathpattern(targetpath: str, pathpattern: str) -> bool:
1185 """Determine whether ``targetpath`` matches the ``pathpattern``."""
1186 # We need to make sure that targetpath and pathpattern are pointing to
1187 # the same directory as fnmatch doesn't threat "/" as a special symbol.
1188 target_parts = targetpath.split("/")
1189 pattern_parts = pathpattern.split("/")
1190 if len(target_parts) != len(pattern_parts):
1191 return False
1192
1193 # Every part in the pathpattern could include a glob pattern, that's why
1194 # each of the target and pathpattern parts should match.
1195 for target, pattern in zip(target_parts, pattern_parts, strict=True):
1196 if not fnmatch.fnmatch(target, pattern):
1197 return False
1198 return True
fnmatch.fnmatch source (Python 3.12, unchanged in current mainline):
def fnmatch(name, pat):
...
name = os.path.normcase(name)
pat = os.path.normcase(pat)
return fnmatchcase(name, pat)
Replace fnmatch.fnmatch with fnmatch.fnmatchcase, which is explicitly documented as "not applying case normalization", so it behaves identically across platforms.
Foo/* and foo/*.foo/something: on Windows, it will find the "attacker" provided target "Foo/something".Reporter: Koda Reef @kodareef5 Advisory edits: Jussi Kukkonen @jku
This page as .md · .json · CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N
GitHub Advisory Database, CC-BY 4.0. Posts quoted belong to their authors.