Skip to content

Errors

The three sentinel errors, what each one means, what its message and hint contain, and what errors.Is matches.

Which errors can be returned?

Every failure wraps exactly one of these three. No other error kind is returned, and *regexp.Regexp is always nil alongside them.

Sentinel Returned when err.Error() contains
ErrPatternTooLong len(pattern) > MaxPatternLength (1024 bytes) regex pattern exceeds maximum length
ErrPatternCompileTimeout the compile did not finish inside the effective deadline, or the caller's context was already cancelled or expired regex pattern compile timed out
ErrPatternInvalid regexp.Compile rejected the pattern's syntax the Go syntax error and the offending expression, then regex pattern is invalid

The bounds behind the first two are documented in Limits and timeouts.

How do I tell the failure modes apart?

Use errors.Is, never ==. The returned value is a wrapped error, so a direct comparison against the sentinel never matches:

re, err := regexutil.CompileBounded(ctx, pattern)
switch {
case errors.Is(err, regexutil.ErrPatternTooLong):
    // rejected before compiling
case errors.Is(err, regexutil.ErrPatternCompileTimeout):
    // too expensive to compile within the deadline
case errors.Is(err, regexutil.ErrPatternInvalid):
    // not valid regex syntax
case err != nil:
    // not expected — do not swallow it
default:
    use(re)
}

Both errors.Is implementations work: the sentinels are ordinary error values, so the standard library's errors.Is matches them just as github.com/cockroachdb/errors does.

Does the error contain the pattern?

Two of the three do not. ErrPatternInvalid does. This distinction decides what is safe to log.

  • ErrPatternTooLong reports the length and nothing else. Its message is the bare sentinel text and its hint reads pattern has 1025 bytes; max is 1024.
  • ErrPatternCompileTimeout reports neither. Its hint is fixed remediation advice: The pattern is too complex to compile safely. Simplify it or use a different match strategy.
  • ErrPatternInvalid wraps Go's syntax error verbatim, and that message quotes the expression that failed to parse. Compiling [unclosed yields:

    error parsing regexp: missing closing ]: `[unclosed`: regex pattern is invalid
    

For most syntax errors the quoted expression is the whole pattern, up to the full 1024 bytes the cap allows.

So an attacker who can reach a compile call, and who sends deliberately invalid patterns, can put content of their choosing into your logs — the log amplification the length and timeout paths are careful to avoid. If your call site logs compile failures from an untrusted source, redact or truncate the ErrPatternInvalid message, or log only the sentinel and a request identifier:

if errors.Is(err, regexutil.ErrPatternInvalid) {
    // Do not log err.Error() here: it quotes attacker-supplied text.
    log.Warn("rejected pattern: invalid syntax", "source", cfgKey)
}

Returning the syntax error to the operator who wrote the pattern is the right thing to do — it is how they find the missing bracket. The hazard is only the log sink, and only when the pattern crossed a trust boundary.

How do I read the hint?

The length and the remediation advice are attached as cockroachdb/errors hints, which are not part of err.Error(). Retrieve them explicitly:

import "github.com/cockroachdb/errors"

hint := errors.FlattenHints(err) // "pattern has 1025 bytes; max is 1024"

Hints are the right thing to show a user; the message is the right thing to return up a call stack. A caller that only ever prints err.Error() will never see the length, which is the single most useful fact about an oversize pattern.

Which errors does errors.Is not match?

Everything except the three sentinels. In particular, none of these hold on any error regexutil returns:

Check Result Why
errors.Is(err, context.DeadlineExceeded) false the timeout path returns the sentinel, not the context's error
errors.Is(err, context.Canceled) false a cancelled caller context also produces ErrPatternCompileTimeout
errors.As(err, &syntaxErr) for *syntax.Error false the syntax error is wrapped as a message, not as a cause in the chain

The last one matters if you wanted to branch on Go's syntax.ErrorCode — you cannot, because the wrap flattens it into text. Match on ErrPatternInvalid and, if you need the detail, show the message to the operator rather than parsing it.