Python's str.lower() breaks IDNA domain encoding, CVE-2026-17084
Some internet standards only support ASCII, so domain names containing other scripts need a Unicode-to-ASCII mapping. IDNA 2003, one such scheme, is built into Python as str.encode('idna') and implemented through the standard library's stringprep module, itself based on the StringPrep algorithm from RFC 3454 and its NamePrep profile from RFC 3491. IDNA 2003 has since been obsoleted by IDNA 2008, which Python users get through the separate idna package on PyPI rather than the built-in encoder.
StringPrep's case folding step, defined in RFC 3454 section 3.2, maps characters through two tables: B.2, which is effectively str.lower() applied under Unicode's case-folding rules, and B.3, a set of exceptions. Python's implementation of table B.2 used the interpreter's own str.lower(), which draws on whichever Unicode version that interpreter ships, for example 17.0.0, as reported by unicodedata.unidata_version. But StringPrep and IDNA 2003 are specified against Unicode 3.2.0 specifically, a fixed dataset Python separately keeps available as unicodedata.ucd_3_2_0. Since Unicode's case-folding rules have changed since version 3.2.0, using the interpreter's current str.lower() instead of the 3.2.0 tables meant the encoder no longer matched the specification it was supposed to implement.
The article demonstrates the gap with a concrete example: encoding the string "ᎠᎠ" (the Cherokee letter U+13A0 doubled) produces 'xn--58da' when the RFC-compliant Unicode 3.2.0 case-folding rules are used, but 'xn--kz9aa' when str.lower() applies Unicode 17.0.0 case folding instead. Two different ASCII encodings for the same domain-name input is precisely what the case-folding step exists to prevent, since systems that validate or compare domain names assume the encoding is deterministic and consistent across implementations. That divergence between implementation and specification is why it was treated as a vulnerability rather than a cosmetic bug, and it has been assigned CVE-2026-17084.
The fix adds new exceptions so str.lower() reproduces Unicode 3.2.0 behavior specifically inside this function: every Unicode code point was checked for where the case folding under the interpreter's shipped Unicode version differs from Unicode 3.2.0's, and those differences were recorded as exceptions. With that change, IDNA 2003 encoding through stringprep is again consistent with RFC 3454.
Bitshift is credited with reporting the vulnerability, Stan Ulbrych with co-developing the remediation, and Marc-Andre Lemburg and Petr Viktorin with reviewing it. The article's author, writing in the role of Security Developer-in-Residence at the Python Software Foundation, a position sponsored by Alpha-Omega, also repeats the general guidance that developers should normally use the idna package (IDNA 2008) rather than the built-in encode("idna") IDNA 2003 path, while noting the older path is still sometimes needed.
Key facts
- Python's built-in IDNA 2003 encoder (str.encode('idna')), implemented via the stringprep module, performed case folding using str.lower(), which follows the interpreter's shipped Unicode version rather than the Unicode 3.2.0 tables RFC 3454 requires.
- Encoding the string "ᎠᎠ" produces 'xn--58da' under the RFC-compliant Unicode 3.2.0 rules but 'xn--kz9aa' when str.lower() applies Unicode 17.0.0 case folding, an inconsistency for the same input.
- The flaw has been assigned CVE-2026-17084.
- The fix adds exceptions so str.lower() reproduces Unicode 3.2.0 behavior inside that specific function, restoring conformance with RFC 3454.
- Bitshift reported the vulnerability, Stan Ulbrych co-developed the remediation, and Marc-Andre Lemburg and Petr Viktorin reviewed it.
Why it matters
The bug shows how a standard-library function that quietly depends on runtime data, str.lower() drawing on whatever Unicode version the interpreter ships, undermined a security-relevant algorithm's compliance with its own specification. IDNA case folding exists so that domain name encoding is canonical and reproducible; once it isn't, two systems can compute different ASCII encodings for the same input, which breaks any logic, validation, comparison, deduplication that assumes a domain has exactly one correct encoded form.
Who it affects
Python code that calls the built-in str.encode('idna'), the IDNA 2003 path built on the stringprep module, is affected. The separate idna package on PyPI, which implements the newer IDNA 2008 standard, does not rely on this table and is unaffected. The article does not state which Python version or versions carry the fix, or whether it has already shipped.
How to use it
The remediation described in the article adds exceptions covering every Unicode code point where the interpreter's shipped case folding disagrees with Unicode 3.2.0, so str.lower() inside stringprep's case-folding function now behaves as the specification requires. Separately from this fix, the author's standing recommendation is to use the idna package (IDNA 2008) rather than the built-in encode("idna") IDNA 2003 path, reserving the older path for the cases that still genuinely need it.
How solid is it
The account comes from the person who reported and fixed the issue, publishing as Security Developer-in-Residence at the Python Software Foundation, a role sponsored by Alpha-Omega. It cites the relevant RFCs (3454, 3491, 5890 through 5893), shows the vulnerable code, and gives a directly reproducible before/after encoding example. The flaw carries an assigned identifier, CVE-2026-17084, and names Bitshift as the reporter, Stan Ulbrych as co-developer of the fix, and Marc-Andre Lemburg and Petr Viktorin as reviewers.
Risks and caveats
The article does not say which Python release the fix has shipped in or whether it is already released, gives no date for discovery, disclosure or the fix, does not state whether the inconsistency was ever exploited, and does not name the author within the text itself. Those specifics remain open until confirmed elsewhere.
“The str.lower() call in this function is a vulnerability!”
— the article's author