symfony/http-foundation · error · InvalidArgumentException

Cannot anonymize less than 0 bytes.

Error message

Cannot anonymize less than 0 bytes.

What it means

IpUtils::anonymize() truncates IP addresses for GDPR-style anonymization. The $v4Bytes and $v6Bytes parameters are documented as int<0,4> and int<0,16>; passing a negative byte count would mean removing more bits than the address has or producing nonsensical output, so the method throws this InvalidArgumentException before doing any work.

Solutions

  1. Clamp the values: $v4Bytes = max(0, min(4, $v4Bytes)); $v6Bytes = max(0, min(16, $v6Bytes));
  2. Validate user/config-supplied byte counts before calling anonymize and reject negatives.
  3. Pass 0 explicitly if you want no truncation (0 is a legal value).
  4. Use native PHP types (int<0,4>) with static analysis to catch negatives before runtime.
  5. Log the offending input value to find which config source produced the negative number.

Example fix

// before
IpUtils::anonymize($ip, $configBytes); // $configBytes = -1: throws

// after
$v4 = max(0, min(4, $configBytes));
$v6 = max(0, min(16, $configBytes));
IpUtils::anonymize($ip, $v4, $v6);
Defensive patterns

Strategy: validation

Validate before calling

// before calling anonymize
$v4Bytes = max(0, (int) $v4Bytes);
$v6Bytes = max(0, (int) $v6Bytes);
if ($v4Bytes < 0 || $v6Bytes < 0) {
    throw new \InvalidArgumentException('Byte counts must be non-negative.');
}

Type guard

function isValidAnonymizeBytes(int $v4, int $v6): bool {
    return $v4 >= 0 && $v4 <= 4 && $v6 >= 0 && $v6 <= 16;
}

Try / catch

try {
    $anon = IpUtils::anonymize($ip, $v4Bytes, $v6Bytes);
} catch (\InvalidArgumentException $e) {
    $anon = IpUtils::anonymize($ip); // safe defaults (1 byte v4, 8 bytes v6)
}

Prevention

When it happens

Trigger: Calling IpUtils::anonymize($ip, -1) or IpUtils::anonymize($ip, 4, -8); computing a byte count from unvalidated config (e.g. a privacy setting that can go negative) and passing it through.

Common situations: Config values like 'anonymize_bytes: -1' read from yaml/env and cast to int without validation; arithmetic that underflows (e.g. $bytes = $userSetting - $extra); tests probing boundary behavior.

Related errors


AI-assisted analysis of symfony/http-foundation@5aea19cd67 (2026-09-13). Data as JSON: /api/errors/d786789919715880. Report an issue: GitHub.

Appendix: source

Thrown at IpUtils.php:210

                return self::setCacheResult($cacheKey, false);
            }
        }

        return self::setCacheResult($cacheKey, true);
    }

    /**
     * Anonymizes an IP/IPv6.
     *
     * Removes the last bytes of IPv4 and IPv6 addresses (1 byte for IPv4 and 8 bytes for IPv6 by default).
     *
     * @param int<0, 4>  $v4Bytes
     * @param int<0, 16> $v6Bytes
     */
    public static function anonymize(string $ip, int $v4Bytes = 1, int $v6Bytes = 8): string
    {
        if ($v4Bytes < 0 || $v6Bytes < 0) {
            throw new \InvalidArgumentException('Cannot anonymize less than 0 bytes.');
        }

        if ($v4Bytes > 4 || $v6Bytes > 16) {
            throw new \InvalidArgumentException('Cannot anonymize more than 4 bytes for IPv4 and 16 bytes for IPv6.');
        }

        /*
         * If the IP contains a % symbol, then it is a local-link address with scoping according to RFC 4007
         * In that case, we only care about the part before the % symbol, as the following functions, can only work with
         * the IP address itself. As the scope can leak information (containing interface name), we do not want to
         * include it in our anonymized IP data.
         */
        if (str_contains($ip, '%')) {
            $ip = substr($ip, 0, strpos($ip, '%'));
        }

        $wrappedIPv6 = false;
        if (str_starts_with($ip, '[') && str_ends_with($ip, ']')) {

View on GitHub (pinned to 5aea19cd67)