{"record":{"id":"6c41d020e80592d8","repo":"yiisoft/yii2","slug":"expected-actual-value-to-be-a-string","errorCode":null,"errorMessage":"Expected actual value to be a string, ","messagePattern":"Expected actual value to be a string, ","errorType":"exception","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"framework/base/Security.php","lineNumber":551,"sourceCode":"\n        return $salt;\n    }\n\n    /**\n     * Performs string comparison using timing attack resistant approach.\n     * @see https://codereview.stackexchange.com/q/13512\n     * @param string $expected string to compare.\n     * @param string $actual user-supplied string.\n     * @return bool whether strings are equal.\n     */\n    public function compareString($expected, $actual)\n    {\n        if (!is_string($expected)) {\n            throw new InvalidArgumentException('Expected expected value to be a string, ' . gettype($expected) . ' given.');\n        }\n\n        if (!is_string($actual)) {\n            throw new InvalidArgumentException('Expected actual value to be a string, ' . gettype($actual) . ' given.');\n        }\n\n        return hash_equals($expected, $actual);\n    }\n\n    /**\n     * Masks a token to make it uncompressible.\n     * Applies a random mask to the token and prepends the mask used to the result making the string always unique.\n     * Used to mitigate BREACH attack by randomizing how token is outputted on each request.\n     * @param string $token An unmasked token.\n     * @return string A masked token.\n     * @since 2.0.12\n     */\n    public function maskToken($token)\n    {\n        // The number of bytes in a mask is always equal to the number of bytes in a token.\n        $mask = $this->generateRandomKey(StringHelper::byteLength($token));\n        return StringHelper::base64UrlEncode($mask . ($mask ^ $token));","sourceCodeStart":533,"sourceCodeEnd":569,"githubUrl":"https://github.com/yiisoft/yii2/blob/66f00d18a29b520f85e8e8f1e32d1e7e7b556cac/framework/base/Security.php#L533-L569","documentation":"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.'","triggerScenarios":"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.","commonSituations":"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.","solutions":["Normalize request input before comparing: cast to string or use ->get('X-Token', '') with an empty-string default.","Treat a missing token as a mismatch, not an exception: if (!is_string($token)) { return false; } before calling compareString().","Validate the shape of decoded JSON payloads (e.g. Yii2 validator with 'string' rule) before using fields in comparisons."],"exampleFix":"// before\n$valid = Yii::$app->security->compareString($expectedSignature, $request->headers->get('X-Signature'));\n// header absent -> headers->get() returns null -> InvalidArgumentException\n\n// after\n$signature = (string) $request->headers->get('X-Signature', '');\n$valid = $signature !== '' && Yii::$app->security->compareString($expectedSignature, $signature);","handlingStrategy":"type-guard","validationCode":"// Normalize request input before comparing\n$token = $request->headers->get('X-Token', '');\n$token = is_string($token) ? $token : '';\nif ($token === '') {\n    return false; // missing token is a mismatch, not an exception\n}\nreturn Yii::$app->security->compareString($expectedToken, $token);","typeGuard":"function requestTokenToString($value): ?string\n{\n    return is_string($value) && $value !== '' ? $value : null;\n}","tryCatchPattern":null,"preventionTips":["Use empty-string defaults when reading headers/cookies/JSON fields used in comparisons","Validate request payload shapes (string rules) before touching security-sensitive values","Cast user-supplied scalars to string once at the boundary, not at each comparison"],"tags":["php","yii2","security","string-comparison","timing-attack","request-input"],"backgroundTag":"wrong-argument-type","analyzedSha":"66f00d18a29b520f85e8e8f1e32d1e7e7b556cac","analyzedAt":"2026-08-17T05:17:23.470Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}