CVE-2026-123816: pyLoad has an authentication bypass in API key validation (check_apikey cache)
Summary
The API-key cache in checkapikey() allows an attacker to authenticate with a forged API key as long as the legitimate key for the same keyid has been used recently.
On a cache hit, the API key secret is never verified, resulting in a complete authentication bypass during the cache lifetime.
---
Affected Components
- src/pyload/core/api/init.py - Api.checkapikey()
- src/pyload/core/database/apikeydatabase.py - checkapikey() - checkkey()
The cache is used by normal API requests through:
- src/pyload/webui/app/helpers.py
which calls:
python api.checkapikey(apikey)
without providing a ttl, meaning the default 300-second cache is always enabled.
---
Root Cause
Successful API key validations are cached in:
python self.apikeycache
using the following structure:
python { keyid: (timestamp, cacheddata) }
The cache key is based only on keyid, and that value is parsed directly from the user-supplied API key rather than being obtained from a trusted lookup.
When a cache hit occurs within the TTL, checkapikey() returns:
python { "success": True, "data": cacheddata }
after checking only:
- expiresat
At no point on this execution path is the supplied secret:
text apikey[-43:]
compared against the stored key hash.
---
The actual secret verification exists only in:
python checkkey()
which correctly uses:
python hmac.comparedigest()
However, this function is only reached on a cache miss through the database layer's checkapikey() implementation.
Furthermore, the cached object cannot be used for verification because it intentionally does not include the stored key hash.
The cached record contains only:
- id - userid - name - createdat - expiresat - lastused
Since keyhash is absent, the cache lacks the information required to validate the presented secret.
As a result, once a legitimate request populates the cache, every subsequent request using the same keyid within the 300-second cache window is accepted without validating the secret.
---
Impact
keyid values are small sequential integers assigned when API keys are created (1, 2, 3, ...), making them trivial to enumerate.
The API key format is also publicly reconstructable from the source code.
An attacker therefore does not need to know any valid API key secret.
They only need a keyid whose legitimate key has been used within the previous five minutes, which is likely on an actively used deployment.
During that cache window, a forged API key follows exactly the same authentication path as a legitimate key and inherits the associated user's permissions.
This results in a complete remote authentication bypass.
I would classify the issue as Critical severity.
---
Estimated CVSS v3.1
AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
(AC:H only because successful exploitation depends on the cache already containing an entry.)
---
Reproduction
1. Create an administrator account.
---
2. Generate an API key.
Example:
text pl11<43-character-secret>
where:
keyid = 1
---
3. Send a legitimate authenticated request.
bash curl \ -H "X-API-Key: pl11<real-43-char-secret>" \ http://127.0.0.1:8000/api/status
Expected response:
text HTTP/1.1 200 OK
---
4. Within 300 seconds, send the same request using a forged API key.
bash curl \ -H "X-API-Key: pl11xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ http://127.0.0.1:8000/api/status
Observed response:
text HTTP/1.1 200 OK
The request succeeds even though the API key secret is entirely incorrect.
---
Control Test
If the forged API key is sent before any legitimate request has populated the cache, the server correctly returns:
text HTTP/1.1 401 Unauthorized
The only difference between the failing and succeeding forged request is whether the cache already contains an entry for that keyid.
This isolates the vulnerability specifically to the cache-hit path.
---
Suggested Fix
Cache hits should never replace secret verification.
Possible approaches include:
- Store a value derived from the complete API key (or its hash) in the cache and verify it using hmac.comparedigest() on every cache hit before returning success.
- Alternatively, key the cache using a value derived from the entire API key secret (for example, a cryptographic hash of the complete API key) rather than using only keyid.
Regardless of implementation, the security invariant should remain:
The API key secret must be validated on every authenticated request, regardless of whether the cache is hit or missed.
Other sources
pyLoad is a free and open-source download manager written in Python. Prior to 0.5.0b3.dev101, Api.checkapikey caches successful validation in self.apikeycache using only the attacker-controlled keyid. After a legitimate key populates the cache, a forged API key with the same keyid reaches the cache-hit path without comparing apikey[-43:] to the stored hash through hmac.comparedigest. Key identifiers are small sequential integers, so an unauthenticated remote attacker can enumerate an active identifier and inherit the cached key owner's permissions during the 300-second cache lifetime. This issue is fixed in version 0.5.0b3.dev101.
— MITRE
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
pip/pyload-ngto a version that resolves this vulnerability.Fixed in 0.5.0b3.dev101 - Upgrade
Upgrade
pyLoadto a version that resolves this vulnerability.Fixed in 0.5.0b3.dev101
Event History
Frequently Asked Questions
Which deployments are exposed to remote exploitation?
Any reachable vulnerable pyLoad API instance is exposed when a legitimate API key has successfully validated within the previous 300 seconds. No attacker authentication or user interaction is required, but the attacker must identify an active key identifier.
What does an attacker need to exploit the cache validation flaw?
The attacker needs a forged API key using the key_id of a legitimately cached key. Because key identifiers are small sequential integers, an unauthenticated remote attacker can enumerate identifiers and target one that is active during the cache lifetime.
What access does successful exploitation provide?
A successful attacker inherits the permissions of the owner of the cached legitimate API key. The reported impact includes high confidentiality, integrity, and availability effects.
How can exposure be reduced if upgrading is not immediately possible?
The provided information identifies the vulnerable condition as a successful API-key validation remaining cached for 300 seconds. Restricting unauthenticated remote access to the pyLoad API would reduce an attacker's ability to enumerate key identifiers during that window.
What version resolves the issue?
The issue is fixed in pyLoad version 0.5.0b3.dev101. Versions prior to 0.5.0b3.dev101 are affected.