From 4d362da9f0e54172ada5d955f16af0be0cb4a32e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Baza=20Garcia=20Rodrigues?= <142340792+joao-baza@users.noreply.github.com> Date: Fri, 13 Feb 2026 04:15:40 -0400 Subject: [PATCH] fix: Prevent user enumeration on password reset endpoint (#13528) ## Description The current password reset endpoint returns different HTTP status codes and messages depending on whether the email exists in the system (200 for existing emails, 404 for non-existing ones). This allows attackers to enumerate valid email addresses via the password reset form. ## Changes ### `app/controllers/devise_overrides/passwords_controller.rb` - Removed the `if/else` branch that returned different responses based on email existence - Now always returns a generic `200 OK` response with the same message regardless of whether the email exists - Uses safe navigation operator (`&.`) to send reset instructions only if the user exists ### `config/locales/en.yml` - Consolidated `reset_password_success` and `reset_password_failure` into a single generic `reset_password` key - New message does not reveal whether the email exists in the system ## Security Impact - **Before**: An attacker could determine if an email was registered by observing the HTTP status code (200 vs 404) and response message - **After**: All requests receive the same 200 response with a generic message, preventing user enumeration This follows [OWASP guidelines for authentication error messages](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html#authentication-responses). Fixes #13527 --- app/controllers/devise_overrides/passwords_controller.rb | 8 ++------ config/locales/en.yml | 3 +-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/app/controllers/devise_overrides/passwords_controller.rb b/app/controllers/devise_overrides/passwords_controller.rb index 00976c3cd..c69541f6f 100644 --- a/app/controllers/devise_overrides/passwords_controller.rb +++ b/app/controllers/devise_overrides/passwords_controller.rb @@ -6,12 +6,8 @@ class DeviseOverrides::PasswordsController < Devise::PasswordsController def create @user = User.from_email(params[:email]) - if @user - @user.send_reset_password_instructions - build_response(I18n.t('messages.reset_password_success'), 200) - else - build_response(I18n.t('messages.reset_password_failure'), 404) - end + @user&.send_reset_password_instructions + build_response(I18n.t('messages.reset_password'), 200) end def update diff --git a/config/locales/en.yml b/config/locales/en.yml index f8d5b119e..07d9b0e2f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -41,8 +41,7 @@ en: invalid_email: 'Please enter a valid email address' authentication_failed: 'Authentication failed. Please check your credentials and try again.' messages: - reset_password_success: Woot! Request for password reset is successful. Check your mail for instructions. - reset_password_failure: Uh ho! We could not find any user with the specified email. + reset_password: Request for password reset is successful. A email with instructions will be sent to your email if it exists. reset_password_saml_user: This account uses SAML authentication. Password reset is not available. Please contact your administrator. login_saml_user: This account uses SAML authentication. Please sign in through your organization's SAML provider. saml_not_available: SAML authentication is not available in this installation.