Every CVE Search/ Get API key

CVE-2026-85990

Leantime: Any Authenticated User Can Self-Escalate to Admin via Unauthorized JSON-RPC Methods

8.8 HighFix available

Leantime is an open source project management system. Prior to 3.10.0, for the provisionally grouped account-creation and plugin-management surfaces, Leantime's JSON-RPC dispatcher in app/Domain/Api/Controllers/Jsonrpc.php exposes public API service methods, while PermissionEnforcer::enforce allows methods that lack a RequiresPermission attribute. A low-privilege authenticated user with an ordinary browser session can send the required X-Requested-With: XMLHttpRequest header and call leantime.rpc.api.api.createApiKeyWithProjects, which reaches Api::createApiKeyWithProjects, with an attacker-chosen role to create an interactive administrator account. The same missing per-method authorization exposes Plugins::installPlugin, Plugins::enablePlugin, Plugins::disablePlugin, and Plugins::removePlugin to ordinary authenticated users, permitting unauthorized plugin management. The complete provisionally grouped issue is fixed in version 3.10.0.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
Low
Privileges needed
Low
User interaction
None

What an attacker can affect

Confidentiality
High
Integrity
High
Availability
High

Exploit likelihood

0.42%Up 0.1 points since Oct 14, 2026
90%50%10%1%

What each source reports

CVE List Score used 8.8

Leantime: Any Authenticated User Can Self-Escalate to Admin via Unauthorized JSON-RPC Methods

Leantime is an open source project management system. Prior to 3.10.0, for the provisionally grouped account-creation and plugin-management surfaces, Leantime's JSON-RPC dispatcher in app/Domain/Api/Controllers/Jsonrpc.php exposes public API service methods, while PermissionEnforcer::enforce allows methods that lack a RequiresPermission attribute. A low-privilege authenticated user with an ordinary browser session can send the required X-Requested-With: XMLHttpRequest header and call leantime.rpc.api.api.createApiKeyWithProjects, which reaches Api::createApiKeyWithProjects, with an attacker-chosen role to create an interactive administrator account. The same missing per-method authorization exposes Plugins::installPlugin, Plugins::enablePlugin, Plugins::disablePlugin, and Plugins::removePlugin to ordinary authenticated users, permitting unauthorized plugin management. The complete provisionally grouped issue is fixed in version 3.10.0.

Record
CVE-2026-85990
CVSS 3.1
8.8 HighCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Weakness
CWE-269, CWE-862
Updated
2026-11-02
EUVDEUVD-2026-98850 8.8

Leantime is an open source project management system. Prior to 3.10.0, for the provisionally grouped account-creation and plugin-management surfaces, Leantime's JSON-RPC dispatcher in app/Domain/Api/Controllers/Jsonrpc.php exposes public API service methods, while PermissionEnforcer::enforce allows methods that lack a RequiresPermission attribute. A low-privilege authenticated user with an ordinary browser session can send the required X-Requested-With: XMLHttpRequest header and call leantime.rpc.api.api.createApiKeyWithProjects, which reaches Api::createApiKeyWithProjects, with an attacker-chosen role to create an interactive administrator account. The same missing per-method authorization exposes Plugins::installPlugin, Plugins::enablePlugin, Plugins::disablePlugin, and Plugins::removePlugin to ordinary authenticated users, permitting unauthorized plugin management. The complete provisionally grouped issue is fixed in version 3.10.0.

Record
EUVD-2026-98850
CVSS 3.1
8.8 HighCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Updated
2026-11-02
GitHub AdvisoriesGHSA-54mm-6729-r5wp 8.8

Leantime: Any Authenticated User Can Self-Escalate to Admin via Unauthorized JSON-RPC Methods

Summary

Any authenticated Leantime user - including the lowest-privilege readonly role - can escalate to full administrative access by calling the JSON-RPC method leantime.rpc.api.api.createApiKeyWithProjects with an attacker-chosen role value. The server creates a new account at that role with no authorization check, and the resulting credentials work as a fully interactive admin login through the ordinary web UI. The same root cause (missing per-method authorization on JSON-RPC-exposed service methods) also leaves the Plugins service's installPlugin, enablePlugin, disablePlugin, and removePlugin methods callable by any authenticated user with no role check, even though their web-UI equivalents correctly require an admin/owner role.

Details

Leantime's JSON-RPC API (app/Domain/Api/Controllers/Jsonrpc.php) reflects any public method on any @api-annotated service class and dispatches it at leantime.rpc.{domain}.{service}.{method}. Per-method authorization is enforced by PermissionEnforcer::enforce() (app/Core/Auth/Permissions/PermissionEnforcer.php), which reads a #[RequiresPermission(...)] PHP attribute off the target method - but when that attribute is absent, enforce() silently returns and allows the call through.

Api::createApiKeyWithProjects(array $values, ?array $projects) (app/Domain/Api/Services/Api.php) is @api-exposed and carries no #[RequiresPermission] attribute. It forwards $values - including a caller-supplied role - directly into createAPIKey() → Users\Repositories\Users::addUser(), with no server-side clamping of role against the calling user's own privilege level:

// app/Domain/Api/Services/Api.php
/**
 * @api
 */
public function createApiKeyWithProjects(array $values, ?array $projects): array|false
{
    $apiKeyValues = $this->createAPIKey($values);   // no #[RequiresPermission], no role check
    ...
}

The equivalent web route (NewApiKey controller) correctly gates account creation behind an admin-only role check (Auth::authOrRedirect([Roles::$admin, ...])), but that gate is bypassed entirely by calling the service directly through JSON-RPC.

Separately, Plugins::installPlugin(), enablePlugin(), disablePlugin(), and removePlugin() (app/Domain/Plugins/Services/Plugins.php, lines ~266/299/320/342) share the same gap: all four are @api-exposed with no #[RequiresPermission], while their web-controller equivalents (Myapps.php, Details.php) correctly require Roles::$owner/Roles::$admin.

Authenticating to the JSON-RPC endpoint with an ordinary browser session cookie (rather than a dedicated API key) is sufficient, provided the request carries an X-Requested-With: XMLHttpRequest header (app/Core/Middleware/StartSession.php) - this is exactly what the application's own first-party JS client already sends, so no special tooling beyond a normal authenticated session is needed.

PoC

A runnable Python PoC (requests library only) reproduces this end-to-end; see the attached poc.py. Steps:

  1. Create/authenticate as a readonly-role account (the lowest privilege level). Confirm it is correctly blocked (HTTP 403) from an admin-only page, e.g. /plugins/myapps.
  2. From that same low-privilege session, send:
    POST /api/jsonrpc
    Content-Type: application/json
    X-Requested-With: XMLHttpRequest
    
    {"jsonrpc":"2.0","id":1,"method":"leantime.rpc.api.api.createApiKeyWithProjects",
     "params":{"values":{"firstname":"poc","role":40,"status":"a"},"projects":[]}}
    
    The server responds HTTP 200 with the newly created account's user/passwordClean and "role":40 (admin) in the JSON body.
  3. Log in through the normal /auth/login form using the returned user/passwordClean. The resulting session now has full, interactive access to /plugins/myapps (HTTP 200) - the exact page that returned 403 in step 1.

Reproduced against a local instance built from this repository's own .dev Docker…

Record
GHSA-54mm-6729-r5wp
CVSS 3.1
8.8 HighCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Weakness
CWE-269, CWE-862
Updated
2026-11-02
NVD 8.8

Leantime is an open source project management system. Prior to 3.10.0, for the provisionally grouped account-creation and plugin-management surfaces, Leantime's JSON-RPC dispatcher in app/Domain/Api/Controllers/Jsonrpc.php exposes public API service methods, while PermissionEnforcer::enforce allows methods that lack a RequiresPermission attribute. A low-privilege authenticated user with an ordinary browser session can send the required X-Requested-With: XMLHttpRequest header and call leantime.rpc.api.api.createApiKeyWithProjects, which reaches Api::createApiKeyWithProjects, with an attacker-chosen role to create an interactive administrator account. The same missing per-method authorization exposes Plugins::installPlugin, Plugins::enablePlugin, Plugins::disablePlugin, and Plugins::removePlugin to ordinary authenticated users, permitting unauthorized plugin management. The complete provisionally grouped issue is fixed in version 3.10.0.

Record
CVE-2026-85990
CVSS 3.1
8.8 HighCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Weakness
CWE-269, CWE-862
Updated
2026-11-02
OSV 8.8

Leantime: Any Authenticated User Can Self-Escalate to Admin via Unauthorized JSON-RPC Methods

Leantime is an open source project management system. Prior to 3.10.0, for the provisionally grouped account-creation and plugin-management surfaces, Leantime's JSON-RPC dispatcher in app/Domain/Api/Controllers/Jsonrpc.php exposes public API service methods, while PermissionEnforcer::enforce allows methods that lack a RequiresPermission attribute. A low-privilege authenticated user with an ordinary browser session can send the required X-Requested-With: XMLHttpRequest header and call leantime.rpc.api.api.createApiKeyWithProjects, which reaches Api::createApiKeyWithProjects, with an attacker-chosen role to create an interactive administrator account. The same missing per-method authorization exposes Plugins::installPlugin, Plugins::enablePlugin, Plugins::disablePlugin, and Plugins::removePlugin to ordinary authenticated users, permitting unauthorized plugin management. The complete provisionally grouped issue is fixed in version 3.10.0.

Record
CVE-2026-85990
CVSS 3.1
8.8 HighCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Weakness
CWE-269, CWE-862
Updated
2026-11-02
OSVGHSA-54mm-6729-r5wp 8.8

Leantime: Any Authenticated User Can Self-Escalate to Admin via Unauthorized JSON-RPC Methods

Summary

Any authenticated Leantime user - including the lowest-privilege readonly role - can escalate to full administrative access by calling the JSON-RPC method leantime.rpc.api.api.createApiKeyWithProjects with an attacker-chosen role value. The server creates a new account at that role with no authorization check, and the resulting credentials work as a fully interactive admin login through the ordinary web UI. The same root cause (missing per-method authorization on JSON-RPC-exposed service methods) also leaves the Plugins service's installPlugin, enablePlugin, disablePlugin, and removePlugin methods callable by any authenticated user with no role check, even though their web-UI equivalents correctly require an admin/owner role.

Details

Leantime's JSON-RPC API (app/Domain/Api/Controllers/Jsonrpc.php) reflects any public method on any @api-annotated service class and dispatches it at leantime.rpc.{domain}.{service}.{method}. Per-method authorization is enforced by PermissionEnforcer::enforce() (app/Core/Auth/Permissions/PermissionEnforcer.php), which reads a #[RequiresPermission(...)] PHP attribute off the target method - but when that attribute is absent, enforce() silently returns and allows the call through.

Api::createApiKeyWithProjects(array $values, ?array $projects) (app/Domain/Api/Services/Api.php) is @api-exposed and carries no #[RequiresPermission] attribute. It forwards $values - including a caller-supplied role - directly into createAPIKey() → Users\Repositories\Users::addUser(), with no server-side clamping of role against the calling user's own privilege level:

// app/Domain/Api/Services/Api.php
/**
 * @api
 */
public function createApiKeyWithProjects(array $values, ?array $projects): array|false
{
    $apiKeyValues = $this->createAPIKey($values);   // no #[RequiresPermission], no role check
    ...
}

The equivalent web route (NewApiKey controller) correctly gates account creation behind an admin-only role check (Auth::authOrRedirect([Roles::$admin, ...])), but that gate is bypassed entirely by calling the service directly through JSON-RPC.

Separately, Plugins::installPlugin(), enablePlugin(), disablePlugin(), and removePlugin() (app/Domain/Plugins/Services/Plugins.php, lines ~266/299/320/342) share the same gap: all four are @api-exposed with no #[RequiresPermission], while their web-controller equivalents (Myapps.php, Details.php) correctly require Roles::$owner/Roles::$admin.

Authenticating to the JSON-RPC endpoint with an ordinary browser session cookie (rather than a dedicated API key) is sufficient, provided the request carries an X-Requested-With: XMLHttpRequest header (app/Core/Middleware/StartSession.php) - this is exactly what the application's own first-party JS client already sends, so no special tooling beyond a normal authenticated session is needed.

PoC

A runnable Python PoC (requests library only) reproduces this end-to-end; see the attached poc.py. Steps:

  1. Create/authenticate as a readonly-role account (the lowest privilege level). Confirm it is correctly blocked (HTTP 403) from an admin-only page, e.g. /plugins/myapps.
  2. From that same low-privilege session, send:
    POST /api/jsonrpc
    Content-Type: application/json
    X-Requested-With: XMLHttpRequest
    
    {"jsonrpc":"2.0","id":1,"method":"leantime.rpc.api.api.createApiKeyWithProjects",
     "params":{"values":{"firstname":"poc","role":40,"status":"a"},"projects":[]}}
    
    The server responds HTTP 200 with the newly created account's user/passwordClean and "role":40 (admin) in the JSON body.
  3. Log in through the normal /auth/login form using the returned user/passwordClean. The resulting session now has full, interactive access to /plugins/myapps (HTTP 200) - the exact page that returned 403 in step 1.

Reproduced against a local instance built from this repository's own .dev Docker…

Record
GHSA-54mm-6729-r5wp
CVSS 3.1
8.8 HighCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Weakness
CWE-269, CWE-862
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
leantime/leantime
Packagist
before 3.10.0
3.10.0
leantime/leantime
Packagist
before 3.10.0, plus listed versions
2.42.4-beta2.4-beta-72.4-beta-82.4.1
Show 45 moreShow less
2.4.22.4.32.4.42.4.52.4.72.4.83.0.03.0.0-beta3.0.13.0.23.0.33.0.43.0.53.0.63.0.73.1.0-beta3.1.13.1.23.1.33.1.43.2.03.2.0-beta3.2.0-beta-23.2.13.33.3.13.3.23.3.33.4.0v2.0v2.0.1v2.0.10v2.0.11v2.0.12v2.0.13v2.0.14v2.0.15v2.0.2v2.0.3v2.0.4v2.0.5v2.0.6v2.0.7v2.0.8v2.0.9
3.10.0

History

History
RecordedChangedSources
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-85990 is a high vulnerability (CVSS 8.8) in Leantime, published October 13, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.42%, the 36th percentile. Fixed in leantime/leantime 3.10.0.

References

Show 4 moreShow less
This page doesn't list everything

Left out here: 96 versions and the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open