{"id":"4530850e28771aaa","repo":"pypa/pip","slug":"a-label-must-not-end-with-a-hyphen","errorCode":null,"errorMessage":"A-label must not end with a hyphen","messagePattern":"A-label must not end with a hyphen","errorType":"validation","errorClass":"IDNAError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/idna/core.py","lineNumber":441,"sourceCode":"    \"\"\"\n    if len(label) > _max_input_length:\n        raise IDNAError(\"Label too long\")\n    if not isinstance(label, (bytes, bytearray)):\n        try:\n            label_bytes = label.encode(\"ascii\")\n        except UnicodeEncodeError:\n            check_label(label)\n            return label\n    else:\n        label_bytes = bytes(label)\n\n    label_bytes = label_bytes.lower()\n    if label_bytes.startswith(_alabel_prefix):\n        label_bytes = label_bytes[len(_alabel_prefix) :]\n        if not label_bytes:\n            raise IDNAError(\"Malformed A-label, no Punycode eligible content found\")\n        if label_bytes.endswith(b\"-\"):\n            raise IDNAError(\"A-label must not end with a hyphen\")\n    else:\n        check_label(label_bytes)\n        return label_bytes.decode(\"ascii\")\n\n    try:\n        label = label_bytes.decode(\"punycode\")\n    except UnicodeError as err:\n        raise IDNAError(\"Invalid A-label\") from err\n    check_label(label)\n    return label\n\n\ndef uts46_remap(domain: str, std3_rules: bool = True, transitional: bool = False) -> str:\n    \"\"\"Apply the UTS #46 character mapping to a domain string.\n\n    Implements the mapping table from `UTS #46 §4\n    <https://www.unicode.org/reports/tr46/>`_: each character is kept,\n    replaced, or rejected based on its status (``V``, ``M``, ``D``, ``3``,","sourceCodeStart":423,"sourceCodeEnd":459,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/idna/core.py#L423-L459","documentation":"Raised by ulabel() (core.py:441) when the bytes after the 'xn--' prefix end with a hyphen ('-'). Punycode labels are defined so the literal hyphens separate the basic ASCII codepoints from the extensions; a trailing hyphen is not valid Punycode syntax, so the A-label is rejected as malformed before decode is even attempted.","triggerScenarios":"Calling idna.ulabel() or idna.decode() with an A-label like 'xn--abc-' where the Punycode portion ends in '-'. Also produced by string manipulation that accidentally appends a hyphen, or by an off-by-one slice of a valid A-label.","commonSituations":"Templated domains that append a trailing '-' for separator reasons, truncation/slicing bugs, copy-paste of a corrupted IDN, or DNS zone files with a typo'd ACE label.","solutions":["Strip a stray trailing hyphen from the Punycode portion if it is clearly a typo, or regenerate the A-label with idna.alabel() from the source Unicode.","Validate the A-label shape (matches ^xn--[A-Za-z0-9-]*[A-Za-z0-9]$) before calling ulabel/decode.","Use decode(domain, display=True) to pass malformed ACE labels through unchanged for display-only consumers."],"exampleFix":"// before\nidna.ulabel('xn--abc-')  # trailing hyphen in Punycode portion\n\n// after\nidna.ulabel('xn--abc')   # no trailing hyphen\n# or rebuild correctly:\nidna.alabel('münchen')  # -> b'xn--mnchen-3ya'","handlingStrategy":"validation","validationCode":"def is_well_formed_alabel(label) -> bool:\n    s = label.decode('ascii') if isinstance(label, (bytes, bytearray)) else label\n    lower = s.lower()\n    if lower.startswith('xn--'):\n        payload = lower[4:]\n        if not payload or payload.endswith('-'):\n            return False\n    return True","typeGuard":"import re\nACE_RE = re.compile(r'^xn--[A-Za-z0-9-]*[A-Za-z0-9]$', re.IGNORECASE)\n\ndef is_valid_ace_label(s) -> bool:\n    return isinstance(s, str) and bool(ACE_RE.match(s))","tryCatchPattern":"import idna\n\ntry:\n    decoded = idna.ulabel(label)\nexcept idna.IDNAError as err:\n    if 'must not end with a hyphen' in str(err):\n        decoded = label.rstrip('-')  # or regenerate via alabel\n    else:\n        raise","preventionTips":["Validate ACE labels with a regex forbidding a trailing hyphen in the payload.","Regenerate A-labels from their Unicode source with idna.alabel() rather than editing them.","Use decode(domain, display=True) for display-only consumers to pass bad labels through."],"tags":["idna","domain","punycode","a-label","malformed"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}