crowdsecurity/crowdsec · error · InvalidFilter
invalid contains value: %w: %w
Error message
invalid contains value: %w: %w
What it means
ExpireDecisionsWithFilter accepts a filter map; when the 'contains' key is present its value must parse as a Go bool via strconv.ParseBool. If the value is not one of 1/t/T/true/TRUE/True/0/f/F/false/FALSE/False, the bool conversion fails and the error is wrapped together with the InvalidFilter sentinel so callers can classify it as a bad filter.
Source
Thrown at pkg/database/decisions.go:236
// ExpireDecisionsWithFilter updates the expiration time to now() for the decisions matching the filter, and returns the updated items
func (c *Client) ExpireDecisionsWithFilter(ctx context.Context, filter map[string][]string) (int, []*ent.Decision, error) {
var (
err error
rng csnet.Range
)
contains := true
// if contains is true, return bans that *contains* the given value (value is the inner)
// else, return bans that are *contained* by the given value (value is the outer)
decisions := c.Ent.Decision.Query().Where(decision.UntilGT(time.Now().UTC()))
for param, value := range filter {
switch param {
case "contains":
contains, err = strconv.ParseBool(value[0])
if err != nil {
return 0, nil, fmt.Errorf("invalid contains value: %w: %w", err, InvalidFilter)
}
case "scopes":
decisions = decisions.Where(decision.ScopeEQ(value[0]))
case "uuid":
decisions = decisions.Where(decision.UUIDIn(value...))
case "origin":
decisions = decisions.Where(decision.OriginEQ(value[0]))
case "value":
decisions = decisions.Where(decision.ValueEQ(value[0]))
case "type":
decisions = decisions.Where(decision.TypeEQ(value[0]))
case "ip", "range":
rng, err = csnet.NewRange(value[0])
if err != nil {
return 0, nil, fmt.Errorf("unable to convert '%s' to int: %w: %w", value[0], err, InvalidIPOrRange)
}
case "scenario":
decisions = decisions.Where(decision.ScenarioEQ(value[0]))View on GitHub (pinned to 909b515798)
Solutions
- Pass a value accepted by strconv.ParseBool: 1, t, T, true, TRUE, True, 0, f, F, false, FALSE, False
- Fix the calling code to serialize booleans properly instead of formatting them as yes/no or on/off
- Trim whitespace/quotes from the value before building the filter map
- Check errors.Is(err, database.InvalidFilter) in the caller to surface a 400-style message to the user
Example fix
// before
c.ExpireDecisionsWithFilter(ctx, map[string][]string{"contains": {"yes"}})
// after
c.ExpireDecisionsWithFilter(ctx, map[string][]string{"contains": {"true"}}) Defensive patterns
Strategy: validation
Validate before calling
func validContains(v string) bool {
_, err := strconv.ParseBool(v)
return err == nil
}
if !validContains(filterVal) { return fmt.Errorf("contains must be a bool, got %q", filterVal) } Try / catch
if _, _, err := client.ExpireDecisionsWithFilter(ctx, filter); err != nil {
if errors.Is(err, database.InvalidFilter) {
return fmt.Errorf("bad filter: %w", err)
}
return err
} Prevention
- Serialize booleans with strconv.FormatBool, never string concatenation
- Trim user input before building the filter map
- Document accepted values (1/t/true, 0/f/false) for CLI users
When it happens
Trigger: Calling ExpireDecisionsWithFilter (directly or via HandleDeletedDecisionsV3, DeleteDecisions, or the cscli decisions CLI) with filter["contains"] set to a non-boolean string such as "yes", "on", "1 ", or "true1".
Common situations: CLI users passing --contains=yes instead of --contains=true; JSON/API clients sending human-style booleans; automation scripts quoting the value wrongly so it arrives as "'true'".
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- invalid filter
- '%s' doesn't exist: %w
- fail to apply StartIpEndIpFilter: %w
- out of bound gid
- unable to parse type
AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06).
Data as JSON: /api/errors/ee3ac4b1f5a85490.
Report an issue: GitHub.