crowdsecurity/crowdsec · error

invalid rdns regex '%s' for bot entry '%s' in %s: %w

Error message

invalid rdns regex '%s' for bot entry '%s' in %s: %w

What it means

Each non-empty element of the "rdns" array is compiled as a case-insensitive regex matched against FCrDNS-verified hostnames. This error is thrown when the pattern is not valid Go RE2 syntax. Compilation happens at load time so bad patterns abort data-file initialization with the offending pattern, entry, and file named.

Source

Thrown at pkg/exprhelpers/botfile.go:112

	for _, r := range entry.Ranges {
		prefix, err := netip.ParsePrefix(r)
		if err != nil {
			return fmt.Errorf("invalid CIDR range '%s' for bot entry '%s' in %s: %w", r, entry.Name, filename, err)
		}

		entry.prefixes = append(entry.prefixes, prefix.Masked())
	}

	for _, p := range entry.RDNS {
		// an empty pattern matches every PTR-confirmed host: almost
		// certainly a mistake, reject it
		if p == "" {
			return fmt.Errorf("empty rdns pattern for bot entry '%s' in %s", entry.Name, filename)
		}

		re, err := compileBotRegex(p)
		if err != nil {
			return fmt.Errorf("invalid rdns regex '%s' for bot entry '%s' in %s: %w", p, entry.Name, filename, err)
		}

		entry.rdnsRegexes = append(entry.rdnsRegexes, re)
	}

	dataFileBots[filename] = append(dataFileBots[filename], entry)

	return nil
}

// parseBotAddr normalizes a source address as found in HTTP contexts:
// bare IP, "ip:port", "[v6]:port". The zone is stripped and IPv4-mapped
// IPv6 is unmapped so comparisons against load-time-parsed IPs/ranges are
// consistent.
func parseBotAddr(s string) (netip.Addr, bool) {
	addr, err := netip.ParseAddr(s)
	if err != nil {
		host, _, splitErr := net.SplitHostPort(s)

View on GitHub (pinned to 909b515798)

Solutions

  1. Fix the syntax error indicated by the wrapped regexp/syntax message (it names the position).
  2. Convert globs to regex: *.googlebot.com becomes (^|\.)googlebot\.com$.
  3. Remove RE2-unsupported constructs (lookaheads (?=...), lookbehinds, backreferences \1) and restructure the pattern.
  4. Anchor the pattern — (^|\.)domain\.tld$ — so attacker-controlled subdomain suffixes cannot match.
  5. Test with Go: regexp.Compile("(?i)" + pattern) before editing the data file.

Example fix

// before
{"name":"gbot","rdns":["(?=.*google)\\1"]}
// after
{"name":"gbot","rdns":["(^|\.)googlebot\.com$"]}
Defensive patterns

Strategy: validation

Validate before calling

for _, p := range entry.RDNS {
	if _, err := regexp.Compile("(?i)" + p); err != nil {
		// reject before FileInit
	}
}
valid := err == nil

Try / catch

if err := exprhelpers.FileInit(botFile, "bots"); err != nil {
	if strings.Contains(err.Error(), "invalid rdns regex") {
		log.Errorf("offending rdns pattern: %v", err)
	}
	return err
}

Prevention

When it happens

Trigger: A bots JSONL entry contains "rdns":[...] where one pattern fails regexp.Compile: unbalanced parentheses, dangling backslash, invalid repetition like "**", or PCRE-only syntax such as lookaheads or backreferences that RE2 rejects.

Common situations: Hand-written domain regexes with typos; copying shell glob patterns (*.googlebot.com) instead of regexes; pasting PCRE patterns from other tools (grep -P, PHP); forgetting to escape dots (cosmetic but the compile error usually indicates worse).

Related errors


AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06). Data as JSON: /api/errors/4548e543756b4fa5. Report an issue: GitHub.