{"id":"0c6ded3ae77d78b0","repo":"pypa/pip","slug":"label-must-not-start-or-end-with-a-hyphen","errorCode":null,"errorMessage":"Label must not start or end with a hyphen","messagePattern":"Label must not start or end with a hyphen","errorType":"validation","errorClass":"IDNAError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/idna/core.py","lineNumber":203,"sourceCode":"        raise IDNAError(\"Label begins with an illegal combining character\")\n    return True\n\n\ndef check_hyphen_ok(label: str) -> bool:\n    \"\"\"Validate the hyphen restrictions for a label.\n\n    Per :rfc:`5891` §4.2.3.1 a label must not start or end with a hyphen\n    (``U+002D``), and must not have hyphens in both the third and fourth\n    positions (the prefix reserved for A-labels).\n\n    :param label: The label to check.\n    :returns: ``True`` if the hyphen restrictions are satisfied.\n    :raises IDNAError: If any of the hyphen restrictions are violated.\n    \"\"\"\n    if label[2:4] == \"--\":\n        raise IDNAError(\"Label has disallowed hyphens in 3rd and 4th position\")\n    if label[0] == \"-\" or label[-1] == \"-\":\n        raise IDNAError(\"Label must not start or end with a hyphen\")\n    return True\n\n\ndef check_nfc(label: str) -> None:\n    \"\"\"Require that a label is in Unicode Normalization Form C.\n\n    :param label: The label to check.\n    :raises IDNAError: If ``label`` differs from its NFC normalisation.\n    \"\"\"\n    if len(label) > _max_input_length:\n        raise IDNAError(\"Label too long\")\n    if unicodedata.normalize(\"NFC\", label) != label:\n        raise IDNAError(\"Label must be in Normalization Form C\")\n\n\ndef valid_contextj(label: str, pos: int) -> bool:\n    \"\"\"Validate the CONTEXTJ rules from :rfc:`5892` Appendix A.\n","sourceCodeStart":185,"sourceCodeEnd":221,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/idna/core.py#L185-L221","documentation":"IDNAError from check_hyphen_ok: per RFC 5891 a label must neither start nor end with a hyphen ('-'). Leading/trailing hyphens are reserved shapes conflicts (and historically used for wildcard/underscore-style abuse) and break DNS interoperability.","triggerScenarios":"A label like '-example', 'example-', or '-' being passed through alabel/check_label. Most commonly the trailing case, where a slugify routine appended a hyphen that was never trimmed.","commonSituations":"slugify() output not stripped of leading/trailing '-'; concatenation 'prefix-' + '' leaving a dangling hyphen; user input normalized to lowercase-then-hyphenated without trimming.","solutions":["Strip leading/trailing hyphens from each label before encoding: label.strip('-').","Fix the slugify/sanitizer to never emit a leading or trailing hyphen.","Treat empty labels (after stripping) as invalid and skip them."],"exampleFix":"# before\nidna.encode('-العرب-')  # must not start or end with a hyphen\n\n# after\nidna.encode('العرب'.strip('-'))","handlingStrategy":"validation","validationCode":"def trim_hyphen_edges(label: str) -> str:\n    return label.strip('-')","typeGuard":"def has_no_edge_hyphens(label: str) -> bool:\n    return bool(label) and label[0] != '-' and label[-1] != '-'","tryCatchPattern":"from idna import IDNAError\ntry:\n    idna.encode(label)\nexcept IDNAError as e:\n    if 'start or end with a hyphen' in str(e):\n        label = label.strip('-')\n    else:\n        raise","preventionTips":["slugify output must be .strip('-') before becoming a DNS label.","Treat post-strip empty labels as invalid and skip them.","Validate edge characters at the input boundary."],"tags":["idna","rfc5891","dns","hyphens","pip"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}