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.
ErrPatternTooLongreports the length and nothing else. Its message is the bare sentinel text and its hint readspattern has 1025 bytes; max is 1024.ErrPatternCompileTimeoutreports neither. Its hint is fixed remediation advice:The pattern is too complex to compile safely. Simplify it or use a different match strategy.-
ErrPatternInvalidwraps Go's syntax error verbatim, and that message quotes the expression that failed to parse. Compiling[unclosedyields:
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.
Related¶
- Limits and timeouts — the bounds that produce these errors.
- Compile an untrusted pattern — the error-handling switch in context.
- Threat model & the two defences — why rejection messages are kept thin in the first place.