Developer platforms, tools and supply chain
Laravel: CVE-2026-114509 TokenGuard authentication bypass
Laravel TokenGuard check
Laravel's built-in token guard ('driver' => 'token') does not reject a crafted non-string credential. A request such as {"api_token": [0]} reaches the database lookup, and on MySQL and MariaDB the lookup can match an unintended existing user. An unauthenticated attacker can then sign in as that user on any route protected by the token guard and take over the account. The reporter, Hristijan Manasijev, described it as a login as an existing victim user.
| CVE | CVE-2026-114509 (GitHub advisory GHSA-f9x3-7vq9-cph6, published 19 October 2026) |
| Severity | 9.2 Critical (CVSS 4.0) |
| Affected | laravel/framework below 12.68.0, and 13.0.0 up to 13.29.0 |
| Fixed | 12.68.0 and 13.29.0, both released 25 August 2026, nearly two months before the advisory. The 11.x branch carries the same is_string() check from v11.56.1, although the advisory does not list it |
| Move to | 12.68.0, 13.29.0 or, on 11.x, 11.56.1 or later: 10.x and 9.x apps upgrade to one of them |
| Needs | A guard that uses the token driver, routes that use it, and MySQL or MariaDB |
| Not the target | Apps that authenticate with Laravel Sanctum or Laravel Passport, which register their own guard drivers |
| Exploited | None confirmed, and no public proof of concept confirmed, as of 21 October 2026 |
The fix is two lines: the guard now requires is_string($token) before it queries the database.
Quick start
-
Download the script. The repository is mirrored on GitHub and Codeberg; pick a base URL.
# BASE from GitHub: https://raw.githubusercontent.com/PrivacySafe/security-patches/refs/heads/main # BASE from Codeberg: https://codeberg.org/PrivacySafe/security-patches/raw/branch/main # curl -fLO "$BASE/laravel/laravel-tokenguard-check.sh" curl -fLO https://raw.githubusercontent.com/PrivacySafe/security-patches/refs/heads/main/laravel/laravel-tokenguard-check.sh -
Read it before running it, then make it executable.
less laravel-tokenguard-check.sh chmod +x laravel-tokenguard-check.sh -
Check this host. By default it searches
/var/www /srv /home /opt; pass directories to narrow it../laravel-tokenguard-check.sh check ./laravel-tokenguard-check.sh check /var/www/myapp -
If you cannot upgrade today, print the protective middleware.
./laravel-tokenguard-check.sh middleware > app/Http/Middleware/RejectNonStringApiToken.php
Commands
| Command | What it does |
|---|---|
check [DIR ...] (default) |
Finds composer.lock files, reads the framework version from vendor/laravel/framework when it is installed (it wins over the lock file) and otherwise from composer.lock, then reads config/auth.php for guards that use the token driver, looks for routes and code that use those guards, and reads DB_CONNECTION from .env |
middleware |
Prints a protective PHP middleware to standard output. It changes nothing by itself |
version |
Prints the script version |
Exit codes: 0 nothing exposed, 1 an affected project looks exposed or is on a branch with no upstream fix, 2 bad arguments.
How to read the result
- Affected version, token guard in use, MySQL or MariaDB: exposed. Upgrade.
- Affected version and no token guard: the vulnerable path is not in use. The script still warns, because custom code could construct a
TokenGuarddirectly. - Token guard defined but nothing references it: a warning. This is a text search of
routes,appandbootstrap, so a guard selected some other way can be missed. - Other database: the advisory describes the bypass on MySQL and MariaDB. The script prints a warning and still recommends upgrading. A project with an unknown database driver is treated as exposed.
- 9.x and 10.x: affected. Upgrade to 12.68.0, 13.29.0 or, on 11.x, 11.56.1 or later.
Many older skeletons ship an api guard with the token driver in config/auth.php whether or not the app uses it. That is why the script looks for routes that use the guard before calling a project exposed.
The protective middleware
middleware prints a class that returns HTTP 401 when api_token arrives as an array or any other non-string value in the query string, the form body or a JSON body. Register it as global middleware that runs before authentication ($middleware->prepend(...) in bootstrap/app.php on Laravel 11 and newer, the first entry of $middleware in app/Http/Kernel.php on older versions). It blocks the crafted input described in the advisory. It is not the fix, and the advisory itself offers no workaround other than upgrading.
Sources
- Laravel News: Critical Token Guard Auth Bypass Fixed in Laravel 13.29 and 12.68
- GitHub advisory GHSA-f9x3-7vq9-cph6
Before you run it
- The fix. Update
laravel/frameworkto 12.68.0, 13.29.0 or, on 11.x, 11.56.1 or later. Apps on 10.x and 9.x upgrade to one of those releases. - It only matters with the built-in token guard. Apps using Laravel Sanctum or Passport register their own guard drivers and are not the target of this bug. The script reads
composer.lockand the installed copy,config/auth.phpfor a guard with the token driver, the routes that use it, and the database driver in.env(the lookup match needs MySQL or MariaDB). - The
middlewarecommand prints a protective middleware to stdout only. It is for apps that cannot upgrade today. The upgrade is the permanent fix. - There is no hunt. No indicators of exploitation have been published.
- It also checks running docker and podman containers, unless you name a directory or set
SCAN_CONTAINERS=0. In each running container, a read-only probe lists the project files this check needs: lockfiles,package.json, route directory names, the few source files that contain'use server',config/auth.phpand similar. Only those files are copied into a private temporary folder, and the normal check judges that folder like a host directory..envis never copied: the probe reads only the oneDB_CONNECTION=line and writes just that line into a minimal.env, so a database password orAPP_KEYstays in the container. A staged symlink is never kept, paths the container reports are validated, and nothing is run in a container beyondfind,grepandtest. The copies are removed on exit. A path shown as.../docker_NAME/PATHis the PATH inside the container NAME. It is skipped when you name directories yourself.
The Security Patches software and this documentation are dedicated to the public domain under CC0 1.0 Universal. Other pages on privacysafe.app/docs are CC BY-SA 4.0 unless the page says otherwise.