Skip to main content
CVE Vulnerability Database
Vulnerability Database/CVE-2026-21621

CVE-2026-21621: Hexpm Privilege Escalation Vulnerability

CVE-2026-21621 is a privilege escalation vulnerability in Hexpm that allows read-only API keys to gain full write access. This article covers technical details, affected versions, security impact, and mitigation.

Published:

CVE-2026-21621 Overview

CVE-2026-21621 is an Incorrect Authorization vulnerability affecting the HexpmWeb.API.OAuthController module in hexpm/hexpm, the backend service powering Hex.pm, the package manager for the Elixir and Erlang ecosystems. This privilege escalation flaw allows attackers with read-only API key access to obtain full write permissions through improper scope handling during OAuth token exchange.

The vulnerability exists in the validate_scopes_against_key/2 routine within lib/hexpm_web/controllers/api/oauth_controller.ex. When a read-only API key (with domain: "api" and resource: "read") is exchanged via the OAuth client_credentials grant flow, the resource qualifier is incorrectly ignored. The resulting JWT receives the broad "api" scope instead of the expected "api:read" scope, effectively granting full API access.

Critical Impact

Attackers who obtain a victim's read-only API key and valid 2FA (TOTP) code can escalate to full write access, enabling them to publish, retire, or modify packages in the Hex.pm ecosystem.

Affected Products

  • hexpm/hexpm from commit 71829cb6f6559bcceb1ef4e43a2fb8cdd3af654b
  • hexpm/hexpm versions before commit 71c127afebb7ed7cc637eb231b98feb802d62999
  • Hex.pm package registry infrastructure

Discovery Timeline

  • 2026-03-05 - CVE-2026-21621 published to NVD
  • 2026-03-05 - Last updated in NVD database

Technical Details for CVE-2026-21621

Vulnerability Analysis

This incorrect authorization vulnerability (CWE-863) stems from a flaw in how the OAuth controller validates and maps API key permissions to OAuth scopes. The validate_scopes_against_key/2 function failed to properly translate granular permission resources into the corresponding OAuth scope restrictions.

When building the set of allowed scopes from key permissions, the original implementation only checked the permission domain without considering the resource qualifier. For API domain permissions, it unconditionally returned the broad "api" scope regardless of whether the original key was restricted to read-only access. This architectural oversight meant that a key explicitly created with resource: "read" would be treated identically to a full-access key during OAuth token generation.

The attack requires two prerequisites: possession of a victim's read-only API key and a valid 2FA (TOTP) code for the victim's account. While this raises the bar for exploitation, read-only keys are often shared more liberally than full-access credentials, and TOTP codes can potentially be obtained through social engineering or other attack vectors.

Root Cause

The root cause lies in the validate_scopes_against_key/2 function which performed insufficient permission-to-scope mapping. The original code only mapped permission domains without respecting the resource qualifier:

text
# Original flawed logic
case permission.domain do
  "api" -> ["api"]  # Resource qualifier ignored!
  "repository" -> ["repository:#{permission.resource}"]
  "repositories" -> [:all_repositories]
  _ -> []
end

This logic treated all API domain permissions identically, ignoring whether the permission was restricted to "read" or "write" operations. The fix introduces a new permission_to_scopes/1 function that properly respects the scope hierarchy.

Attack Vector

The attack is network-based and requires low privileges (possession of a read-only API key) and some prerequisites (valid 2FA code). An attacker follows this exploitation path:

  1. Obtain a victim's read-only API key (potentially from logs, shared CI/CD configurations, or credential exposure)
  2. Acquire a valid TOTP code for the victim's account
  3. Exchange the read-only API key via the OAuth client_credentials grant
  4. Receive a JWT with the broad "api" scope instead of "api:read"
  5. Use the elevated JWT to create a new full-access API key without expiration
  6. Perform write operations such as publishing malicious packages or retiring legitimate ones

The security patch introduces proper scope hierarchy handling:

text
+  def permission_to_scopes(%{domain: "api", resource: nil}),
+    do: ["api", "api:read", "api:write"]
+
+  def permission_to_scopes(%{domain: "api", resource: "write"}),
+    do: ["api:write", "api:read"]
+
+  def permission_to_scopes(%{domain: "api", resource: "read"}), do: ["api:read"]
+
+  def permission_to_scopes(%{domain: "repository", resource: resource}),
+    do: ["repository:#{resource}"]
+
+  def permission_to_scopes(%{domain: "repositories"}), do: [:all_repositories]
+  def permission_to_scopes(_permission), do: []

Source: GitHub Commit Update

Detection Methods for CVE-2026-21621

Indicators of Compromise

  • JWT tokens with "api" scope originating from keys that were created with read-only permissions
  • Unexpected API key creation events, especially keys with unrestricted permissions or no expiration
  • Package publish, retire, or modification events from accounts that should only have read access
  • OAuth token exchange requests followed immediately by privileged write operations

Detection Strategies

  • Audit OAuth token exchange logs for scope escalation patterns where the input key has resource: "read" but the resulting JWT has full "api" scope
  • Monitor for newly created API keys with full write permissions, particularly those without expiration dates
  • Implement alerting on package registry write operations (publish, retire, modify) and correlate with the originating API key's expected permissions
  • Review authentication logs for suspicious patterns combining TOTP validation with OAuth token exchanges

Monitoring Recommendations

  • Enable detailed audit logging for all OAuth client_credentials grant operations
  • Implement scope validation monitoring that alerts when JWT scopes exceed the source key's permissions
  • Track API key lineage to detect when elevated keys are created using tokens from restricted keys
  • Monitor for bulk or unusual package modifications that may indicate compromised write access

How to Mitigate CVE-2026-21621

Immediate Actions Required

  • Update hexpm to commit 71c127afebb7ed7cc637eb231b98feb802d62999 or later immediately
  • Audit all recently created API keys for unexpected full-access permissions
  • Review package modification history for any unauthorized changes
  • Consider rotating API keys for sensitive accounts, especially those with package publishing privileges
  • Enable additional monitoring on OAuth token exchange endpoints

Patch Information

The vulnerability is fixed in commit 71c127afebb7ed7cc637eb231b98feb802d62999. The patch introduces the permission_to_scopes/1 function in lib/hexpm/permissions.ex that properly respects the permission hierarchy, ensuring read-only keys can only generate "api:read" scoped JWTs. The validate_scopes_against_key/2 function in the OAuth controller is updated to use this new function.

For detailed patch information, see the GitHub Security Advisory GHSA-739m-8727-j6w3.

Workarounds

  • Restrict distribution of read-only API keys and treat them with similar sensitivity to full-access keys until patched
  • Implement additional authentication factors or IP restrictions for OAuth token exchange endpoints
  • Temporarily disable the OAuth client_credentials grant flow if not critical to operations
  • Monitor and alert on any API key creation operations until the patch is deployed
bash
# Example: Audit API keys created recently for unexpected permissions
# Review hexpm database for keys created in the last 30 days
mix run -e "
  Hexpm.Accounts.Key
  |> Hexpm.Repo.all()
  |> Enum.filter(&(DateTime.compare(&1.inserted_at, DateTime.add(DateTime.utc_now(), -30, :day)) == :gt))
  |> Enum.each(&IO.inspect(&1.permissions))
"

Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

Default Legacy - Prefooter | Experience the World’s Most Advanced Cybersecurity Platform

Experience the Most Advanced Cybersecurity Platform

See how the world’s most intelligent, autonomous cybersecurity platform can protect your organization today and into the future.