projectdiscovery/katana · warning
publicsuffix: empty label in domain %q
Error message
publicsuffix: empty label in domain %q
What it means
getDomainRDNandRDN extracts the root domain and eTLD+1 from a hostname, but first rejects hostnames containing an empty label — a leading dot, trailing dot, or consecutive dots. Such input would produce nonsense from publicsuffix lookups, so the function fails early with this publicsuffix-prefixed error. validateDNS surfaces it when evaluating DNS-based scope rules.
Source
Thrown at pkg/utils/scope/scope.go:168
}
// matchesDomainOrSubdomain reports whether host equals domain or is one of its
// subdomains (i.e. host ends with "."+domain). Matching is case-insensitive
// because DNS labels are case-insensitive. Requiring the leading dot enforces a
// label boundary, so look-alike hosts that only share domain as a raw string
// suffix (e.g. evilexample.com vs example.com) are not considered in scope.
func matchesDomainOrSubdomain(host, domain string) bool {
host = strings.ToLower(host)
domain = strings.ToLower(domain)
return host == domain || strings.HasSuffix(host, "."+domain)
}
// getDomainRDNandRDN extracts and returns the root domain name (RDN) and the
// effective top-level domain plus one label (eTLD+1) from the given hostname.
// It returns empty strings and an error if the hostname cannot be parsed.
func getDomainRDNandRDN(domain string) (string, string, error) {
if strings.HasPrefix(domain, ".") || strings.HasSuffix(domain, ".") || strings.Contains(domain, "..") {
return "", "", fmt.Errorf("publicsuffix: empty label in domain %q", domain)
}
suffix, _ := publicsuffix.PublicSuffix(domain)
if len(domain) <= len(suffix) {
return domain, "", nil
}
i := len(domain) - len(suffix) - 1
if domain[i] != '.' {
return domain, "", nil
}
return domain[1+strings.LastIndex(domain[:i], "."):], domain[1+strings.LastIndex(domain[:i], ".") : len(domain)-len(suffix)-1], nil
}
View on GitHub (pinned to e3e742739c)
Solutions
- Normalize the hostname before scope evaluation: strip a single trailing dot and reject/trim empty labels (net.SplitHostPort for host:port, url.Parse for URLs).
- Fix the upstream source of the malformed domain — log the input to find whether it's an empty Host header or a join bug.
- Handle wildcard scope entries (".example.com") as patterns, not as hostnames passed to the DNS check.
- Skip DNS validation for hosts that fail net.ParseIP/lookup and fall back to regex scope matching.
Example fix
// before
rdn, etld1, err := getDomainRDNandRDN(host) // host = "example.com."
// after
host = strings.TrimSuffix(strings.TrimSpace(host), ".")
if host == "" || strings.Contains(host, "..") {
return fmt.Errorf("invalid hostname %q", host)
}
rdn, etld1, err := getDomainRDNandRDN(host) Defensive patterns
Strategy: validation
Validate before calling
func validHost(h string) bool {
h = strings.TrimSuffix(h, ".")
return h != "" && !strings.HasPrefix(h, ".") && !strings.Contains(h, "..")
} Type guard
func isParseableDomain(s string) bool {
s = strings.TrimSuffix(s, ".")
return s != "" && strings.Contains(s, ".") && !strings.Contains(s, "..")
} Try / catch
rdn, etld1, err := getDomainRDNandRDN(host)
if err != nil {
if strings.Contains(err.Error(), "empty label in domain") {
host = strings.TrimSuffix(strings.TrimSpace(host), ".")
host = strings.TrimPrefix(host, ".")
rdn, etld1, err = getDomainRDNandRDN(host)
}
} Prevention
- Always normalize hostnames (trim trailing dot, trim spaces) before scope evaluation.
- Parse hosts out of URLs with url.Parse instead of manual string slicing.
- Reject wildcard entries (".example.com") upstream so they never reach DNS validation.
- Guard against empty Host headers on incoming requests before scope matching.
When it happens
Trigger: Calling validateDNS (via scope matching) with a hostname like ".example.com", "example.com.", or "a..b.com" — the empty-label precheck fires and returns this error instead of attempting the publicsuffix split.
Common situations: URLs parsed from raw input leaving a trailing dot (FQDN form "example.com."); split/join bugs building hostnames from parts ("" joined between labels); scope rules applied to empty or malformed Host header values; wildcard entries like ".example.com" fed as literal hostnames.
Related errors
AI-assisted analysis of projectdiscovery/katana@e3e742739c (2026-09-03).
Data as JSON: /api/errors/95c28798f353475d.
Report an issue: GitHub.