News

Python's str.lower() Was a Security Vulnerability in IDNA 2003

A subtle bug in CPython's IDNA 2003 implementation used the interpreter's Unicode version for case folding, diverging from the RFC 3454 spec. CVE-2026-17084 fixes it by adding exceptions to match Unicode 3.2.0 behavior.

August 26, 2026· 2 min read· Source: sethmlarson.dev
Python's str.lower() Was a Security Vulnerability in IDNA 2003

Python's str.lower() is a workhorse — but when it's used inside the IDNA 2003 codec, it can open a security hole. The issue: IDNA 2003, defined by RFC 3491, relies on StringPrep (RFC 3454), which specifies case folding based on Unicode 3.2.0. Python's str.lower(), however, uses whatever Unicode version the interpreter ships with — currently 17.0.0. That mismatch means the codec can produce different domain names than the spec intended, potentially leading to confusion or spoofing.

The vulnerable code in CPython's stringprep module looked like this:

def map_table_b3(code):
    r = b3_exceptions.get(ord(code))
    if r is not None: return r
    return code.lower()

The str.lower() call uses the interpreter's Unicode data, not the Unicode 3.2.0 data that StringPrep requires. Python actually ships a unicodedata.ucd_3_2_0 module specifically for this purpose, but the code didn't use it for case folding.

The practical impact: certain Unicode characters, like Cherokee letter 'Ꭰ' (U+13A0), fold differently under Unicode 17.0.0 than under 3.2.0. This changes the ASCII-compatible encoding (ACE) output:

# RFC 3454 compliant value
>>> "ᎠᎠ".encode("idna")
'xn--58da'

# Value if using Unicode 17.0.0 case-folding
>>> "ᎠᎠ".encode("idna")
'xn--kz9aa'

That discrepancy means two different Unicode strings could map to the same IDNA label, or the same string could map to different labels depending on the Python version — a classic recipe for domain confusion attacks.

The fix, merged in CPython PR #155293, adds explicit exceptions for every codepoint where str.lower() diverges from Unicode 3.2.0 behavior. Now IDNA 2003 is consistent with the spec, regardless of the interpreter's Unicode version.

This was reported by Bitshift, with co-development by Stan Ulbrych, and reviewed by Marc-Andre Lemburg and Petr Viktorin. Tracked as CVE-2026-17084.

For most developers, the takeaway is: use the idna package (IDNA 2008) instead of str.encode('idna') (IDNA 2003). But if you must support legacy IDNA, this fix matters.

The str.lower() call in this function is a vulnerability!
Manul X Editorial
IDNA 2003 vs IDNA 2008
At a glance
AspectIDNA 2003IDNA 2008
SpecificationRFC 3491 (StringPrep)RFC 5890-5893
Unicode version3.2.0Latest (e.g., 15.1)
Python supportstr.encode('idna')idna package
Case foldingStringPrep B.2/B.3UTS #46
Security statusObsoleted, had CVE-2026-17084Actively maintained