Security 10 min read Markdown

Your Laravel APP_KEY Leaked: What an Attacker Can Do, and How to Rotate It Safely

Researchers found more than 260,000 Laravel APP_KEYs in public GitHub repositories, and hundreds of live applications still using them. What a leaked key actually exposes, why APP_PREVIOUS_KEYS can keep a leaked key working, and the rotation steps in the right order.

Matt King
Matt King
October 2, 2026
Last updated: October 2, 2026
Your Laravel APP_KEY Leaked: What an Attacker Can Do, and How to Rotate It Safely

In July 2025, GitGuardian and Synacktiv published joint research on leaked Laravel encryption keys. They extracted more than 260,000 APP_KEY values from public GitHub repositories and found over 600 Laravel applications that were vulnerable as a result. Most of the leaks, 63%, came from committed .env files.

If your key is in a public repository, a forum post, a screenshot or an old .env that was once reachable on your web server, assume it is known. This post covers what that key unlocks, what it does not, and how to replace it without leaving the old one working.


What APP_KEY protects

APP_KEY is the key for Laravel's encrypter. Everything that goes through it is only as private as the key:

  • Cookies. Laravel encrypts every cookie it sets, including the session cookie.
  • Crypt, encrypt() and decrypt(), anywhere in your code or packages.
  • Encrypted Eloquent casts, such as encrypted, encrypted:array and encrypted:object.
  • Signed URLs. Signatures are computed with the application key, so a known key means forgeable temporary and signed links.

What it does not protect:

  • Password hashes. These are bcrypt or Argon2 hashes and have nothing to do with APP_KEY.
  • Password reset tokens. The key is used when generating the random token, but the stored copy is hashed, so the key alone does not let anyone forge a reset.

What an attacker can do with it

Read and forge encrypted data

With the key, an attacker can decrypt any encrypted value they can get hold of: cookies captured from a browser, values in URLs or hidden fields, and encrypted columns if they also obtain a database dump. They can also produce new encrypted values your application will accept as genuine, and forge signed URLs, for example to reach a download or unsubscribe link meant for someone else.

Remote code execution

The serious case is deserialization. decrypt() unserializes the decrypted value by default. If an attacker can make your application decrypt something they encrypted, they can send a PHP object gadget chain (built with a tool like phpggc) and have it unserialized on your server. Depending on the packages installed, that ends in remote code execution.

Modern Laravel does not unserialize cookie values by default, which closed the original issue, CVE-2018-15133. But the risk did not go away. It moved to wherever an application still decrypts attacker-controlled input:

  • decrypt() on request data. Invoice Ninja before 5.10.43 called decrypt() on a value from a route reachable without authentication (CVE-2024-55555).
  • The cookie session driver. With SESSION_DRIVER=cookie, the whole session lives in an encrypted cookie. Crater Invoice was vulnerable through its laravel_session cookie (CVE-2024-55556).
  • Custom cookie or token handling that serializes objects. Snipe-IT before 7.0.10 is another example (CVE-2024-48987).

Synacktiv released laravel-crypto-killer, which tests captured cookies against lists of known keys. That is how the research found hundreds of live applications using leaked keys. Testing your own cookies against public key lists is cheap for an attacker.

Default keys from commercial scripts

One finding stands out. Many of the vulnerable applications had not leaked their key at all. They were running a commercial script or template from a marketplace such as CodeCanyon that shipped with an APP_KEY already set, and nobody changed it. The single most common key belonged to one point-of-sale product, found on 561 servers.

If you deployed a purchased Laravel product, check that APP_KEY was regenerated on install. If it matches the one in the original download, it is public.


How to rotate it safely

The order matters, and so does one Laravel feature that can quietly undo the whole exercise.

1. Generate a new key without touching .env

php artisan key:generate --show

--show prints a new key instead of writing it to .env, which is what you want when the real value lives in your hosting platform's environment settings.

2. Decide whether you need APP_PREVIOUS_KEYS

Laravel 11 added graceful key rotation. List old keys in APP_PREVIOUS_KEYS (comma separated) and Laravel encrypts with the new key but tries each previous key when decrypting:

APP_KEY=base64:NEW_KEY_HERE
APP_PREVIOUS_KEYS=base64:OLD_KEY_HERE

For a routine rotation, this is great: users stay logged in and encrypted columns keep working. For a leaked key, it is a trap. While the leaked key is in APP_PREVIOUS_KEYS, your application still accepts data encrypted with it, including payloads an attacker built. You have rotated the key and left the door open.

So:

  • No encrypted columns or long-lived encrypted data? Do not use APP_PREVIOUS_KEYS at all. Set the new key and accept that everyone is logged out.
  • Encrypted columns you must keep? Use APP_PREVIOUS_KEYS only while you re-encrypt them (step 3), then remove it.

3. Re-encrypt stored data

With the old key temporarily in APP_PREVIOUS_KEYS, decrypt and re-encrypt each encrypted column. Encryption always uses the current key, so a simple pass does it:

use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;

DB::table('integrations')
    ->whereNotNull('api_secret')
    ->orderBy('id')
    ->chunkById(500, function ($rows) {
        foreach ($rows as $row) {
            DB::table('integrations')
                ->where('id', $row->id)
                ->update(['api_secret' => Crypt::encryptString(Crypt::decryptString($row->api_secret))]);
        }
    });

Laravel's encrypted casts store values encrypted with encryptString, so this works for those columns too. Run it for every encrypted column, check the results, then remove the leaked key from APP_PREVIOUS_KEYS and redeploy.

4. Rotate everything that leaked with it

In the GitGuardian research, 35% of leaked keys were exposed alongside other secrets. If the key leaked through a .env file, so did your database password, mail credentials, payment keys and third-party API tokens. Rotate all of them, not just APP_KEY.

5. Clean up the source of the leak

Remove the file from the repository and its history, check forks and mirrors, and add .env to .gitignore if it was missing. Then make sure your web server cannot serve it: see Laravel .env file exposed for the server configuration.

6. Look for signs it was used

Check logs for decryption errors (DecryptException, "The MAC is invalid") from unexpected IPs, requests carrying unusually large cookies, and unfamiliar files or processes on the server. A spike in decryption errors around the time of the leak is a sign someone was testing keys.


Preventing the next one

  • Never commit .env, and turn on secret scanning for your repositories.
  • Regenerate the key on every new install, especially for purchased scripts and starter templates.
  • Avoid decrypt() on user input. If you need to pass state through the client, use signed values or store it server side.
  • Prefer the database or Redis session driver over the cookie driver.
  • Harden unserialization. Laravel 13 defaults the cache serializable_classes option to false for exactly this scenario, and new applications serialize sessions as JSON.

The open source stackshield/scanner checks that APP_KEY is set and not a known default from inside your application. From the outside, a free StackShield scan checks whether your .env and other sensitive files are reachable on your live site, which is how many of these keys leak in the first place. For the fix-focused version of this topic, see our guide to weak or default APP_KEY values.

Free security check

Is your Laravel app exposed right now?

34% of Laravel apps we scan have at least one critical issue, and most teams do not find out until something breaks. The free scan checks your live app in 60 seconds. Then StackShield re-runs every check after each deploy, so a fix you ship today does not quietly regress next week.

18% have debug mode on
72% missing security headers
12% have exposed .env
Scan My App Free No signup for the scan. Continuous monitoring on a 14-day trial, no card.

Frequently Asked Questions

What can an attacker do with a leaked Laravel APP_KEY?

They can encrypt and decrypt anything your application encrypts: cookies, encrypted model attributes, and values passed through encrypt() and decrypt(). They can also forge signed URLs. Where the application decrypts attacker-supplied data and unserializes it, for example through decrypt() on a request parameter or the cookie session driver, a leaked key can lead to remote code execution through PHP gadget chains.

Does a leaked APP_KEY expose user passwords?

No. Laravel stores passwords as bcrypt or Argon2 hashes, which do not use APP_KEY. Password reset tokens are also stored hashed, so the key alone does not let an attacker forge one. Encrypted columns are different: if an attacker also has a copy of your database, the leaked key decrypts them.

How do I rotate a Laravel APP_KEY?

Generate a new key with php artisan key:generate --show, set it as APP_KEY in every environment, and, only if you have encrypted data to migrate, put the old key in APP_PREVIOUS_KEYS while you re-encrypt it. Then remove the old key from APP_PREVIOUS_KEYS. Expect all users to be logged out when the old key stops being accepted.

Is it safe to put a leaked key in APP_PREVIOUS_KEYS?

Only briefly. Laravel tries every key in APP_PREVIOUS_KEYS when decrypting, so while the leaked key is listed there, data encrypted with it is still accepted, including payloads an attacker crafted. Use it only for the time it takes to re-encrypt stored data, then remove it.

How do I know if my APP_KEY has leaked?

Search your repositories and their history for APP_KEY=base64, including forks and old public projects, and check whether your .env was ever committed or served publicly. Also check whether your key is a default shipped with a commercial script or template: researchers found many vulnerable applications were using keys that came with the product.

Stay Updated on Laravel Security

Get actionable security tips, vulnerability alerts, and best practices for Laravel apps.