crowdsecurity/crowdsec · error

unable to convert '%s' to int: %w

Error message

unable to convert '%s' to int: %w

What it means

CountDecisionsByValue parses its value argument into a csnet.Range with csnet.NewRange before counting matching decisions. If the value is not a valid IP address or CIDR range, NewRange fails and the error is wrapped (the 'convert to int' wording is legacy; it means an invalid IP/range value).

Source

Thrown at pkg/database/decisions.go:385

	// XXX: do we want 500 or 404 here?
	if err != nil || len(toUpdate) == 0 {
		c.Log.Warningf("ExpireDecisionByID : %v (nb expired: %d)", err, len(toUpdate))
		return 0, nil, fmt.Errorf("decision with id '%d' doesn't exist: %w", decisionID, DeleteFail)
	}

	if len(toUpdate) == 0 {
		return 0, nil, ItemNotFound
	}

	count, err := c.ExpireDecisions(ctx, toUpdate)

	return count, toUpdate, err
}

func (c *Client) CountDecisionsByValue(ctx context.Context, value string, since *time.Time, onlyActive bool) (int, error) {
	rng, err := csnet.NewRange(value)
	if err != nil {
		return 0, fmt.Errorf("unable to convert '%s' to int: %w", value, err)
	}

	contains := true
	decisions := c.Ent.Decision.Query()

	decisions, err = decisionIPFilter(decisions, contains, rng)
	if err != nil {
		return 0, fmt.Errorf("fail to apply StartIpEndIpFilter: %w", err)
	}

	if since != nil {
		decisions = decisions.Where(decision.CreatedAtGT(*since))
	}

	if onlyActive {
		decisions = decisions.Where(decision.UntilGT(time.Now().UTC()))
	}

View on GitHub (pinned to 909b515798)

Solutions

  1. Validate the value with net.ParseIP or net.ParseCIDR before calling
  2. Ensure the value is a plain IP (1.2.3.4) or full CIDR (1.2.3.0/24), with no extra prefixes or whitespace
  3. Return a 400 to upstream callers when the wrapped error surfaces instead of retrying

Example fix

// before
count, err := client.CountDecisionsByValue(ctx, r.URL.Query().Get("value"), nil, true)
// after
v := strings.TrimSpace(r.URL.Query().Get("value"))
if net.ParseIP(v) == nil {
    if _, _, cidrErr := net.ParseCIDR(v); cidrErr != nil {
        http.Error(w, "value must be an IP or CIDR", http.StatusBadRequest)
        return
    }
}
count, err := client.CountDecisionsByValue(ctx, v, nil, true)
Defensive patterns

Strategy: validation

Validate before calling

func validIPOrRange(v string) bool {
    if net.ParseIP(v) != nil { return true }
    _, _, err := net.ParseCIDR(v)
    return err == nil
}
if !validIPOrRange(value) { return ErrInvalidValue }

Try / catch

if _, err := client.CountDecisionsByValue(ctx, value, since, onlyActive); err != nil {
    if strings.Contains(err.Error(), "unable to convert") {
        return status.Error(codes.InvalidArgument, "value must be an IP or CIDR")
    }
    return err
}

Prevention

When it happens

Trigger: Calling CountDecisionsByValue with value="bad-input", an empty string, a hostname, or a malformed CIDR like "1.2.3/32".

Common situations: Bouncer/API callers forwarding raw user input as the value; empty query parameters; IPv6 written with typos; values missing their /prefix for ranges.

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


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