Summary
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.
Vulnerable code
tuf/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)
Attack
- A TUF repository with two path-based delegations whose patterns differ only in case, for example,
Foo/*andfoo/*. - The "attacker" delegation is listed BEFORE the "legit" delegation in the delegation order.
- The client searches for
foo/something: on Windows, it will find the "attacker" provided target "Foo/something".
Exploitability caveats
- The attack needs a repository configuration with case-colliding delegation path patterns. The attacker must control one of the delegated roles.
- Delegation ordering matters: the attacker-controlled role must be visited BEFORE the legit role in the pre-order walk.
- The client must run on Windows. No effect on Linux/macOS.
Credit
Reporter: Koda Reef @kodareef5
Advisory edits: Jussi Kukkonen @jku
Impact
GHSA-QP9X-WP8F-QGJJ has a CVSS score of 4.0 (Medium). The vector is requires local access, no privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (7.0.0); upgrading removes the vulnerable code path.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Remediation advice
Replace fnmatch.fnmatch with fnmatch.fnmatchcase, which is explicitly documented as "not applying case normalization", so it behaves identically across platforms.
Frequently Asked Questions
- What is GHSA-QP9X-WP8F-QGJJ? GHSA-QP9X-WP8F-QGJJ is a medium-severity security vulnerability in tuf (pip), affecting versions <= 6.0.0. It is fixed in 7.0.0.
- How severe is GHSA-QP9X-WP8F-QGJJ? GHSA-QP9X-WP8F-QGJJ has a CVSS score of 4.0 (Medium). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
- Which versions of tuf are affected by GHSA-QP9X-WP8F-QGJJ? tuf (pip) versions <= 6.0.0 is affected.
- Is there a fix for GHSA-QP9X-WP8F-QGJJ? Yes. GHSA-QP9X-WP8F-QGJJ is fixed in 7.0.0. Upgrade to this version or later.
- Is GHSA-QP9X-WP8F-QGJJ exploitable, and should I be worried? Whether GHSA-QP9X-WP8F-QGJJ is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether GHSA-QP9X-WP8F-QGJJ is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix GHSA-QP9X-WP8F-QGJJ? Upgrade
tufto 7.0.0 or later.