XTLS/Xray-core · error
pattern string does not conform to Letter-Digit-Hyphen (LDH)
Error message
pattern string does not conform to Letter-Digit-Hyphen (LDH) subset
What it means
While normalizing a domain-matcher pattern to lowercase/ASCII, every byte must belong to the LDH subset (letters, digits, hyphen, dot). Any other ASCII punctuation (underscore, wildcard '*', slash, etc.) in a pattern that this normalizer processes is rejected; non-ASCII is converted via punycode instead.
Source
Thrown at common/geodata/strmatcher/matchers.go:171
// 2. If any non-ASCII characters, domain are converted from Internationalized domain name to Punycode.
func ToDomain(pattern string) (string, error) {
for {
isASCII, hasUpper := true, false
for i := 0; i < len(pattern); i++ {
c := pattern[i]
if c >= utf8.RuneSelf {
isASCII = false
break
}
switch {
case 'A' <= c && c <= 'Z':
hasUpper = true
case 'a' <= c && c <= 'z':
case '0' <= c && c <= '9':
case c == '-':
case c == '.':
default:
return "", errors.New("pattern string does not conform to Letter-Digit-Hyphen (LDH) subset")
}
}
if !isASCII {
var err error
pattern, err = idna.Punycode.ToASCII(pattern)
if err != nil {
return "", err
}
continue
}
if hasUpper {
pattern = strings.ToLower(pattern)
}
break
}
return pattern, nil
}
View on GitHub (pinned to 7d214f8b09)
Solutions
- Replace '_' with '-' in the pattern, or route such names to a matcher that accepts them (e.g. keyword:/regexp: rules instead of domain matching).
- If a wildcard was intended, ensure the rule type supports it rather than passing '*' into this normalizer.
Example fix
// before "domain": ["domain:my_host.example.com"] // after "domain": ["keyword:my_host.example.com"]
Defensive patterns
Strategy: validation
Validate before calling
var ldhRe = regexp.MustCompile(`^[A-Za-z0-9.-]+$`)
func isLDH(pattern string) bool {
if !ldhRe.MatchString(pattern) { return false }
for _, r := range pattern { if r >= utf8.RuneSelf { return true /* punycode path */ } }
return true
} Type guard
func isLDHPattern(p string) bool {
for _, r := range p {
if r >= utf8.RuneSelf { return true }
if !((r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '-' || r == '.') { return false }
}
return true
} Prevention
- Route underscore/wildcard-containing names through keyword: or regexp: matchers instead of domain matchers.
- Sanitize hostnames to LDH before feeding strmatcher builders.
When it happens
Trigger: Patterns like "my_host.example.com" or "*.example.com" routed into the substring/domain matcher path that enforces LDH; underscore is the classic offender.
Common situations: Using underscore subdomains (valid in DNS but not LDH) with matchers that assume hostnames; feeding URL paths or wildcard strings where a plain domain is expected.
Related errors
- failed to parse user
- bridge tag is empty
- bridge domain is empty
- portal tag is empty
- portal domain is empty
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/c652ae3bac073301.
Report an issue: GitHub.