A critical vulnerability in Laravel's built-in token API guard lets an unauthenticated visitor log in as an existing user without knowing that user's token. The Laravel team fixed it in August 2026, but the advisory only went public on 19 October 2026, and details and a public proof of concept followed within days. It is rated CVSS 9.2. Most modern Laravel apps are not exposed, because the guard has to be switched on in your config, but if yours is, this is a same-day update. Below: who is affected, a 2-minute check, the fix, and why it happens. I confirmed the root cause on a real MariaDB database and compared the code before and after the fix.
The short version
-
What: authentication bypass in
Illuminate\Auth\TokenGuard, tracked as GHSA-f9x3-7vq9-cph6, CVSS v4.0 9.2. -
Who: apps that use a guard with
'driver' => 'token'inconfig/auth.php, with users stored in MySQL or MariaDB. -
Fix: update
laravel/frameworkto 13.29.0 or newer (13.x), or 12.68.0 or newer (12.x). - Not listed as affected: Sanctum and Passport, which register their own guards. The session guard is not listed either.
- Exploitation: a public proof of concept has been reported. As of the latest report I found (Cyber Kendra, 21 October), no attacks in the wild had been seen.
Step 1: check whether you use the token guard
# 1. Is a guard using the token driver?
grep -n "'driver' => 'token'" config/auth.php
# 2. Which framework version is installed?
composer show laravel/framework | grep versions
# 3. Does Composer already know about this advisory?
composer audit
If the first command prints nothing, you are not using the vulnerable guard. Also check routes protected with something like auth:api and make sure that guard is not the token driver. Older Laravel skeletons (up to about Laravel 7) shipped an api guard with the token driver, so long-lived apps are the most likely to still have it. If composer audit lists GHSA-f9x3-7vq9-cph6, you have the confirmation.
Step 2: update
composer update laravel/framework
composer show laravel/framework | grep versions
You want 13.29.0+ on Laravel 13 or 12.68.0+ on Laravel 12. Then deploy and restart your PHP workers. The advisory offers no other workaround, so do not treat a rule in a firewall as a replacement.
Versions: what I verified in the source
Sources disagree on version details, so I compared the actual code in the tagged releases. The fix is a type check in TokenGuard.php:
| Release | Has the is_string() check? |
|---|---|
| 13.26.1 and older | No |
| 13.27.0, 13.28.0, 13.29.0 | Yes |
| 12.67.0 | No |
| 12.68.0 | Yes |
| 11.56.0 | No |
| 11.56.1 | Yes |
| 10.49.0 | No |
The code change appears from 13.27.0 on, but the advisory and Laravel News list 13.29.0 as the fixed version for Laravel 13, so move to 13.29.0 or newer and you are covered either way. The fix commit is dated 24 August 2026 and the releases 25 August 2026. On Laravel 11, one report says no patch exists while another lists 11.56.1; the source shows 11.56.1 does contain the check. On 10.x, the release I checked (10.49.0) has no fix, so check the advisory for your branch or upgrade to a supported version.
Why it happens
The explanation below follows the published analysis, and I tested each link in the chain.
- Before the fix, the guard only checked that the incoming token was not empty. It did not check that it was a string.
- A non-string value, such as a list, passes that check and reaches the user lookup, where it is turned into a database query with a list of values.
- MySQL and MariaDB compare text columns with numbers by converting the text to a number, and text that does not start with a digit becomes 0.
Step 3 is easy to confirm yourself without any exploit. On MariaDB, with two users whose tokens start with letters, I ran three queries:
| Query | Rows matched |
|---|---|
where api_token = 0 |
2 (every user) |
where api_token in (0) |
2 (every user) |
where api_token = '0' |
0 |
A numeric value matched every row, and the same value as text matched none. So a non-string "token" can match a real user's row, which is why the guard authenticates someone who never knew the real token. According to the analysis, the attacker cannot choose which user they become, but that is little comfort when one of them is an admin. This is also why the advisory is specific to MySQL and MariaDB; I did not test other databases, so I cannot tell you they are safe.
I also compared the guard code before (13.26.1) and after (13.29.0), using a stub user provider that records what reaches the database. Before the fix, a non-string token was passed to the provider and the guard resolved a user. After the fix, validate() returned false, user() returned null, and nothing non-string reached the provider. That is a unit-level check with a stand-in provider, not a test against a running app.
What about hashed tokens?
If your guard has 'hash' => true, an array token makes PHP's hash() throw a TypeError in the user() path (I confirmed that call fails with an array). That produces an error instead of a match, but the advisory does not exempt hashed setups, so update regardless.
If you can't update today
There is no official workaround. The practical steps are to check whether you really use the guard, and if you do not need it, remove the token guard from config/auth.php so no route can use it. In the longer term, consider moving API authentication to Sanctum, which is not listed as affected. Review access logs for unusual successful logins on token-protected routes, and treat accounts that never should have been reachable as suspect.
A second, smaller fix in the same release
Laravel News also reports a medium-severity issue (CVSS 6.5, GHSA-5wcm-3v2w-8362) in the JSON:API resources added in Laravel 12.45, where a read-only request could trigger a destructive operation on a related table. It is fixed in 13.27.0 and 12.68.0, so the same update covers it. I did not test this one.
Sources
- Laravel News: Critical Token Guard Auth Bypass Fixed in Laravel 13.29 and 12.68
- Cyber Kendra: Laravel Patches Critical Token Guard Auth Bypass
- Lilting: Laravel TokenGuard bypass on MySQL
Originally published on DEV Talk.
Top comments (0)