Files
Noah Ross a94b8bdb4e Fix API Key Limit Race Condition Bypass (#5620)
The API key creation endpoint checks that a user has fewer than 25 keys
before creating a new one. The problem is that the count was read from
an eager-loaded collection (`$user->apiKeys->count()`) with no lock
held, so concurrent requests could both pass the check and each create a
key, pushing the user past the 25-key cap.

The fix wraps the count check and key creation in a single database
transaction with `lockForUpdate()` on the query. Only one request at a
time can evaluate and modify the count, closing the race window.

### Proof of Concept

Run this in the browser console while authenticated with a user that has
24 API keys:

```js
(async () => {
    const makeKey = (desc) => fetch('/api/client/account/api-keys', {
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
        'X-Requested-With': 'XMLHttpRequest',
        'X-XSRF-TOKEN':
  decodeURIComponent(document.cookie.match(/XSRF-TOKEN=([^;]+)/)[1]),
      },
      body: JSON.stringify({ description: desc, allowed_ips: [] }),
    });

    const [r1, r2] = await Promise.all([makeKey('0024'), makeKey('0025')]);
    console.log('0024:', r1.status, (await r1.text()).slice(0, 200));
    console.log('0025:', r2.status, (await r2.text()).slice(0, 200));
})();
```

On the old code, both requests can return 200 (you may need to run this
a few times to hit the race window). After the fix, the second request
correctly returns a 400 error.
2026-05-23 11:16:18 -07:00
..