pypa/pip · error · IDNAError

Domain too long

Error message

Domain too long

What it means

Raised by uts46_remap() (core.py:475) as a defensive guard when the input domain is longer than _max_input_length (1024 characters) before the per-codepoint UTS #46 mapping loop. This bounds work on oversized input; the real 253/254-octet DNS limit is enforced separately in encode()/decode() via valid_string_length().

Source

Thrown at src/pip/_vendor/idna/core.py:475

    Implements the mapping table from `UTS #46 §4
    <https://www.unicode.org/reports/tr46/>`_: each character is kept,
    replaced, or rejected based on its status (``V``, ``M``, ``D``, ``3``,
    ``I``). The result is returned in Normalisation Form C.

    :param domain: The full domain name to remap.
    :param std3_rules: If ``True``, apply the stricter STD3 ASCII rules
        (status ``3`` codepoints raise instead of being kept or mapped).
    :param transitional: If ``True``, use transitional processing (status
        ``D`` codepoints are mapped instead of kept). Transitional
        processing has been removed from UTS #46 and this option is
        retained only for backwards compatibility.
    :returns: The remapped domain, in Normalisation Form C.
    :raises InvalidCodepoint: If the domain contains a disallowed
        codepoint under the chosen rules.
    :raises IDNAError: If ``domain`` exceeds the defensive input length limit.
    """
    if len(domain) > _max_input_length:
        raise IDNAError("Domain too long")
    from .uts46data import uts46_replacements, uts46_starts, uts46_statuses

    output = ""

    for pos, char in enumerate(domain):
        code_point = ord(char)
        i = code_point if code_point < 256 else bisect.bisect_right(uts46_starts, code_point) - 1
        status = chr(uts46_statuses[i])
        replacement: Optional[str] = uts46_replacements[i]

        # UTS #46 §4: V is always valid, D is deviation (kept unless transitional),
        # 3 is disallowed-STD3 (kept unmapped if std3_rules is off and no mapping).
        keep_as_is = (
            status == "V" or (status == "D" and not transitional) or (status == "3" and not std3_rules and replacement is None)
        )
        # M is mapped, 3-with-replacement and transitional D fall through to the
        # same replacement output path.
        use_replacement = replacement is not None and (

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Extract the hostname component (e.g. via urllib.parse.urlsplit(s).hostname) before passing it to idna.
  2. Reject inputs longer than 254 characters at the trust boundary — no valid domain is longer.
  3. Cap input length to 1024 (or better, 254) and validate before calling uts46_remap/encode/decode.

Example fix

// before
idna.encode(long_url, uts46=True)  # full URL > 1024 chars

// after
from urllib.parse import urlsplit
host = urlsplit(long_url).hostname or long_url
if len(host) > 253:
    raise ValueError('hostname too long')
idna.encode(host, uts46=True)
Defensive patterns

Strategy: validation

Validate before calling

def is_acceptable_domain_length(domain: str, limit=254) -> bool:
    return isinstance(domain, str) and len(domain) <= limit

if not is_acceptable_domain_length(domain):
    raise ValueError('domain too long for UTS #46 processing')
remapped = idna.uts46_remap(domain)

Type guard

def is_bounded_str(s, limit=254) -> bool:
    return isinstance(s, str) and len(s) <= limit

Try / catch

import idna

try:
    encoded = idna.encode(domain, uts46=True)
except idna.IDNAError as err:
    if 'too long' in str(err):
        raise ValueError('input exceeds domain length limits') from err
    raise

Prevention

When it happens

Trigger: Calling uts46_remap() directly, or calling encode()/decode() with uts46=True, on a domain string longer than 1024 characters — typically a non-domain string (URL, token, log line) routed into the mapping function.

Common situations: Passing a full URL instead of a bare hostname to idna, a proxy feeding the whole request line into the IDNA path, fuzz tests with unbounded lengths, or concatenated domains missing separators.

Related errors


AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04). Data as JSON: /data/errors/3cd678ae356bc3b9.json. Report an issue: GitHub.