pypa/pip · error · IDNAError
Malformed A-label, no Punycode eligible content found
Error message
Malformed A-label, no Punycode eligible content found
What it means
Raised by ulabel() (core.py:439) when the label has the ACE prefix 'xn--' but nothing follows it — i.e. the Punycode payload is empty. There is no codepoint content to decode, so the A-label is structurally malformed. This is a literal 'xn--' with zero characters after the prefix.
Source
Thrown at src/pip/_vendor/idna/core.py:439
:returns: The U-label as a Unicode string.
:raises IDNAError: If the label is malformed or fails validation.
"""
if len(label) > _max_input_length:
raise IDNAError("Label too long")
if not isinstance(label, (bytes, bytearray)):
try:
label_bytes = label.encode("ascii")
except UnicodeEncodeError:
check_label(label)
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 §4View on GitHub (pinned to d7d0d0a394)
Solutions
- Detect and reject the bare 'xn--' label upstream — it is never a valid A-label.
- Regenerate the correct A-label from the intended Unicode label using idna.alabel(unicode_label) rather than hand-building 'xn--...'.
- If decoding untrusted input, use decode(domain, display=True) so a malformed 'xn--' label is passed through unchanged instead of raising.
Example fix
// before
idna.ulabel('xn--') # empty Punycode payload
// after
idna.ulabel('xn--example-' ) # real Punycode payload
# or rebuild from the Unicode form:
idna.alabel('exämple') # -> b'xn--exmple-cua' Defensive patterns
Strategy: validation
Validate before calling
def is_well_formed_alabel(label) -> bool:
s = label.decode('ascii') if isinstance(label, (bytes, bytearray)) else label
if s.lower().startswith('xn--'):
return len(s) > 4 # must have content after the prefix
return True
if not is_well_formed_alabel(label):
raise ValueError("malformed A-label: bare 'xn--'") Type guard
import re
ACE_RE = re.compile(r'^xn--[A-Za-z0-9-]+[A-Za-z0-9]$', re.IGNORECASE)
def is_valid_ace_label(s) -> bool:
return isinstance(s, str) and bool(ACE_RE.match(s)) Try / catch
import idna
try:
decoded = idna.ulabel(label)
except idna.IDNAError as err:
if 'Malformed A-label' in str(err):
# bare 'xn--'; drop or regenerate the label
decoded = label # pass through unchanged
else:
raise Prevention
- Never hand-build 'xn--' strings; always generate A-labels with idna.alabel().
- Validate ACE labels against ^xn--<non-empty payload>$ before decode.
- Use decode(domain, display=True) for display-only consumers to pass bad labels through.
When it happens
Trigger: Calling idna.ulabel() or idna.decode() with the string 'xn--' (or 'xn--' as a whole label in a domain like 'xn--.example.com'), or with input that has been truncated/malformed so the Punycode portion is empty.
Common situations: Hand-edited or templated hostnames where the Punycode suffix was dropped, string-slicing bugs that truncate after 'xn--', copy-paste of a partial IDN, or test fixtures using 'xn--' as a placeholder.
Related errors
- A-label must not end with a hyphen
- Invalid A-label
- 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/dc5b7180cabbe04f.json.
Report an issue: GitHub.