yiisoft/yii2 · error · InvalidArgumentException

Expected actual value to be a string,

Error message

Expected actual value to be a string, 

What it means

Thrown by yii\base\Security::compareString() when the $actual argument (the user-supplied side of the comparison) is not a PHP string. hash_equals() requires both parameters to be strings, so the method validates each with is_string() and reports the actual gettype() in the message, e.g. 'Expected actual value to be a string, NULL given.'

Source

Thrown at framework/base/Security.php:551

        return $salt;
    }

    /**
     * Performs string comparison using timing attack resistant approach.
     * @see https://codereview.stackexchange.com/q/13512
     * @param string $expected string to compare.
     * @param string $actual user-supplied string.
     * @return bool whether strings are equal.
     */
    public function compareString($expected, $actual)
    {
        if (!is_string($expected)) {
            throw new InvalidArgumentException('Expected expected value to be a string, ' . gettype($expected) . ' given.');
        }

        if (!is_string($actual)) {
            throw new InvalidArgumentException('Expected actual value to be a string, ' . gettype($actual) . ' given.');
        }

        return hash_equals($expected, $actual);
    }

    /**
     * Masks a token to make it uncompressible.
     * Applies a random mask to the token and prepends the mask used to the result making the string always unique.
     * Used to mitigate BREACH attack by randomizing how token is outputted on each request.
     * @param string $token An unmasked token.
     * @return string A masked token.
     * @since 2.0.12
     */
    public function maskToken($token)
    {
        // The number of bytes in a mask is always equal to the number of bytes in a token.
        $mask = $this->generateRandomKey(StringHelper::byteLength($token));
        return StringHelper::base64UrlEncode($mask . ($mask ^ $token));

View on GitHub (pinned to 66f00d18a2)

Solutions

  1. Normalize request input before comparing: cast to string or use ->get('X-Token', '') with an empty-string default.
  2. Treat a missing token as a mismatch, not an exception: if (!is_string($token)) { return false; } before calling compareString().
  3. Validate the shape of decoded JSON payloads (e.g. Yii2 validator with 'string' rule) before using fields in comparisons.

Example fix

// before
$valid = Yii::$app->security->compareString($expectedSignature, $request->headers->get('X-Signature'));
// header absent -> headers->get() returns null -> InvalidArgumentException

// after
$signature = (string) $request->headers->get('X-Signature', '');
$valid = $signature !== '' && Yii::$app->security->compareString($expectedSignature, $signature);
Defensive patterns

Strategy: type-guard

Validate before calling

// Normalize request input before comparing
$token = $request->headers->get('X-Token', '');
$token = is_string($token) ? $token : '';
if ($token === '') {
    return false; // missing token is a mismatch, not an exception
}
return Yii::$app->security->compareString($expectedToken, $token);

Type guard

function requestTokenToString($value): ?string
{
    return is_string($value) && $value !== '' ? $value : null;
}

Prevention

When it happens

Trigger: Passing a value read with a null default as $actual: Yii::$app->request->headers->get('X-Token') when the header is absent; $_COOKIE value via ArrayHelper::getValue($_COOKIE, 'key') returning null; a JSON body field that is a number/object instead of a string; casting mistakes where an array of tokens is passed instead of one token.

Common situations: CSRF/auth-token checks against optional request headers or cookies that may legitimately be missing; API clients sending numeric tokens that json_decode() turns into ints; file-upload or webhook handlers receiving structured JSON where a scalar was expected.

Related errors


AI-assisted analysis of yiisoft/yii2@66f00d18a2 (2026-08-17). Data as JSON: /api/errors/6c41d020e80592d8. Report an issue: GitHub.