mirror of
https://github.com/pterodactyl/panel.git
synced 2026-06-01 13:29:31 -05:00
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.