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

CVE-2026-72917: AnythingLLM Auth Bypass Vulnerability

CVE-2026-72917 is an authentication bypass flaw in AnythingLLM that allows attackers to exploit the password recovery flow using a single recovery code to gain unauthorized account access, including admin accounts.

Published:

CVE-2026-72917 Overview

AnythingLLM contains an authentication bypass vulnerability in its unauthenticated account-recovery flow. The flaw affects versions 1.0.0 through 1.15.0 and resides in server/utils/PasswordRecovery/index.js. The recoverAccount() function deduplicates raw recoveryCodes values before trimming whitespace. An attacker who knows a target username and a single valid recovery code can submit that code twice with different surrounding whitespace to satisfy the two-code check. The same normalized value also matches the same stored bcrypt hash instead of consuming a distinct hash. This enables account takeover, including administrator accounts, through the POST /api/system/recover-account and POST /api/system/reset-password endpoints in multi-user mode.

Critical Impact

An attacker with knowledge of one recovery code and a username can take over any account, including administrator accounts, without prior authentication.

Affected Products

  • AnythingLLM versions 1.0.0 through 1.15.0
  • Deployments running in multi-user mode
  • Self-hosted Mintplex-Labs AnythingLLM instances exposing the recovery API

Discovery Timeline

  • 2026-08-10 - CVE-2026-72917 published to NVD
  • 2026-08-12 - Last updated in NVD database

Technical Details for CVE-2026-72917

Vulnerability Analysis

AnythingLLM requires users to submit two distinct recovery codes to reset a forgotten password. The two-code requirement is designed to raise the bar for unauthenticated recovery. The original implementation constructed a Set from the raw input array first, then trimmed and validated each element. This ordering allowed "abc-uuid" and " abc-uuid " to appear as two distinct Set entries because Set uniqueness compared the untrimmed strings. Both entries reduced to the same UUID after trimming and both matched the same stored bcrypt hash. The recovery flow then issued a password-reset token, which an attacker submitted to POST /api/system/reset-password to complete account takeover. The weakness is classified as [CWE-180] Incorrect Behavior Order: Validate Before Canonicalize.

Root Cause

The root cause is an ordering error in input canonicalization. Deduplication ran against raw, whitespace-padded strings before trimming and validation. A single valid UUID recovery code could therefore satisfy both slots of the two-code check. A second logic error compounded the flaw: the hash-matching loop did not remove a matched hash from the candidate pool, so the same stored hash could authorize both submitted codes.

Attack Vector

The attack requires network access to the AnythingLLM API and prior knowledge of a target username plus one valid recovery code. An attacker sends POST /api/system/recover-account with a JSON body containing the username and an array such as ["<code>", " <code> "]. The server responds with a password-reset token. The attacker then calls POST /api/system/reset-password with the token and a chosen password. Multi-user mode must be enabled for the recovery endpoints to be reachable.

javascript
   if (allUserHashes.length < 4)
     return { success: false, error: "Invalid recovery codes." };
 
-  // If they tried to send more than two unique codes, we only take the first two
-  const uniqueRecoveryCodes = [...new Set(recoveryCodes)]
-    .map((code) => code.trim())
-    .filter((code) => validate(code)) // we know that any provided code must be a uuid v4.
-    .slice(0, 2);
+  const uniqueRecoveryCodes = [
+    ...new Set(
+      recoveryCodes
+        .map((code) => (typeof code === "string" ? code.trim() : ""))
+        .filter((code) => validate(code))
+    ),
+  ].slice(0, 2);
   if (uniqueRecoveryCodes.length !== 2)
     return { success: false, error: "Invalid recovery codes." };
 
+  const unmatchedHashes = [...allUserHashes];
   const validCodes = uniqueRecoveryCodes.every((code) => {
-    let valid = false;
-    allUserHashes.forEach((hash) => {
-      if (bcrypt.compareSync(code, hash)) valid = true;
-    });
-    return valid;
+    const index = unmatchedHashes.findIndex((hash) =>
+      bcrypt.compareSync(code, hash)
+    );
+    if (index === -1) return false;
+    unmatchedHashes.splice(index, 1);

Source: GitHub Commit 61766d0. The patch trims and validates recovery codes before deduplication and tracks matched hashes in unmatchedHashes to ensure each code consumes a distinct stored hash.

Detection Methods for CVE-2026-72917

Indicators of Compromise

  • Requests to POST /api/system/recover-account containing recoveryCodes array entries that differ only in leading or trailing whitespace.
  • Successful POST /api/system/reset-password calls that follow a recovery request within a short time window for privileged accounts.
  • Unexpected password changes on administrator accounts followed by new session activity from unfamiliar IP addresses.

Detection Strategies

  • Parse application access logs and flag recoverAccount request bodies where trimmed UUIDs across the recoveryCodes array collide.
  • Alert on any successful password reset for accounts flagged with the admin role in the AnythingLLM database.
  • Correlate reset-password events with subsequent workspace configuration or user management API calls that indicate takeover activity.

Monitoring Recommendations

  • Enable verbose logging on the AnythingLLM server and forward events to a centralized log platform for retention and correlation.
  • Monitor authentication and recovery endpoints for anomalous request rates, particularly from single source IP addresses probing multiple usernames.
  • Track administrator login history and password change audit records for out-of-band modifications.

How to Mitigate CVE-2026-72917

Immediate Actions Required

  • Upgrade AnythingLLM to a version above 1.15.0 that includes commit 61766d0.
  • Rotate all existing recovery codes for every user, prioritizing administrator accounts.
  • Force a password reset for accounts whose recovery codes may have been exposed through prior phishing, shared documents, or backup leaks.
  • Restrict network exposure of the AnythingLLM API to trusted networks or place it behind an authenticating reverse proxy.

Patch Information

The fix is published in commit 61766d06b77b903f66dc4afd8dffb3a39012db14 and documented in GHSA-vv8w-wg6r-hq56. The patch trims and validates each recovery code before applying Set deduplication and consumes each stored bcrypt hash exactly once during verification.

Workarounds

  • Disable multi-user mode until the patched version is deployed, since the recovery endpoints only operate in that mode.
  • Block POST /api/system/recover-account at a reverse proxy or web application firewall when self-service recovery is not required.
  • Add a WAF rule that rejects recoverAccount request bodies where trimmed recoveryCodes array entries are not unique.
bash
# Example nginx rule to block the recovery endpoint
location = /api/system/recover-account {
    return 403;
}

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.