pypa/pip · error · IDNAError
Invalid A-label
Error message
Invalid A-label
What it means
Raised by ulabel() (core.py:449) when the bytes after 'xn--' are present and not trailing-hyphen but fail to Punycode-decode — Python's codecs.ascii/punycode decode raises UnicodeError, which is wrapped as IDNAError('Invalid A-label'). The label looks like an A-label structurally but its payload is not valid Punycode.
Source
Thrown at src/pip/_vendor/idna/core.py:449
return label
else:
label_bytes = bytes(label)
label_bytes = label_bytes.lower()
if label_bytes.startswith(_alabel_prefix):
label_bytes = label_bytes[len(_alabel_prefix) :]
if not label_bytes:
raise IDNAError("Malformed A-label, no Punycode eligible content found")
if label_bytes.endswith(b"-"):
raise IDNAError("A-label must not end with a hyphen")
else:
check_label(label_bytes)
return label_bytes.decode("ascii")
try:
label = label_bytes.decode("punycode")
except UnicodeError as err:
raise IDNAError("Invalid A-label") from err
check_label(label)
return label
def uts46_remap(domain: str, std3_rules: bool = True, transitional: bool = False) -> str:
"""Apply the UTS #46 character mapping to a domain string.
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 isView on GitHub (pinned to d7d0d0a394)
Solutions
- Treat the value as untrusted and reject it; a non-decodable A-label cannot be turned into a meaningful U-label.
- If you only need a displayable form, call decode(domain, display=True) which passes the bad label through lowercased instead of raising.
- Regenerate the A-label from a known-good Unicode source via idna.alabel() rather than trusting externally-supplied 'xn--' strings.
Example fix
// before
idna.decode('xn--!!invalid!!.example.com') # payload not Punycode
// after
idna.decode('xn--!!invalid!!.example.com', display=True) # pass-through
# or reject upstream:
try:
idna.ulabel(label)
except idna.IDNAError:
label = label.lower() # graceful fallback for display Defensive patterns
Strategy: try-catch
Validate before calling
def looks_like_valid_punycode(label) -> bool:
# cheap heuristic: try to round-trip through punycode
s = label.decode('ascii') if isinstance(label, (bytes, bytearray)) else label
if not s.lower().startswith('xn--'):
return True
payload = s[4:]
try:
payload.encode('ascii').decode('punycode')
return True
except (UnicodeError, ValueError):
return False Type guard
import re
ACE_RE = re.compile(r'^xn--[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?$', re.IGNORECASE)
def is_plausible_ace_label(s) -> bool:
return isinstance(s, str) and bool(ACE_RE.match(s)) Try / catch
import idna
try:
decoded = idna.decode(domain)
except idna.IDNAError as err:
if 'Invalid A-label' in str(err):
# payload not decodable; display-only fallback or reject
decoded = idna.decode(domain, display=True)
else:
raise Prevention
- Treat externally-supplied 'xn--' labels as untrusted; never assume they decode.
- Use decode(domain, display=True) in URL/HTTP display paths so bad labels pass through.
- Round-trip-validate stored A-labels (alabel -> ulabel -> alabel) at ingest time.
When it happens
Trigger: Calling idna.ulabel() or idna.decode() with a label that has the 'xn--' prefix and a non-empty, non-trailing-hyphen payload that nonetheless is not valid Punycode — e.g. 'xn--!!', 'xn--zzz invalid', or a label where the Punycode extension integers are out of range. Also hit when an unrelated string happens to start with 'xn--'.
Common situations: Corrupted DNS responses, hand-typed or templated ACE labels with invalid characters, adversarial/obfuscated input probing the decoder, or misrouted opaque tokens that coincidentally begin with 'xn--'.
Related errors
- Malformed A-label, no Punycode eligible content found
- A-label must not end with a hyphen
- Label has disallowed hyphens in 3rd and 4th position
- Codepoint {_unot(cp_value)} not allowed at position {pos + 1
- Codepoint {_unot(cp_value)} at position {pos + 1} of {label!
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/708049a4d4a6905d.json.
Report an issue: GitHub.