CVE-2026-58503 Overview
CVE-2026-58503 is a user enumeration vulnerability in the Frappe full-stack web application framework. The flaw exists in the reset_password endpoint, which returned distinct responses depending on whether a submitted username existed, was disabled, or was restricted (Administrator). Unauthenticated attackers can send POST requests to this endpoint and observe the differing responses to enumerate valid usernames. The issue is classified as [CWE-203] Observable Discrepancy and affects Frappe versions prior to 15.106.0 and 16.16.0. Fixes are available in Frappe 15.106.0 and 16.16.0, which normalize the response so identical output is returned regardless of account state.
Critical Impact
Unauthenticated attackers can enumerate valid Frappe user accounts through the public password reset endpoint, enabling targeted credential attacks.
Affected Products
- Frappe Framework versions prior to 15.106.0
- Frappe Framework versions prior to 16.16.0
- Applications built on Frappe (including ERPNext deployments) using vulnerable framework versions
Discovery Timeline
- 2026-07-10 - CVE-2026-58503 published to NVD
- 2026-07-13 - Last updated in NVD database
Technical Details for CVE-2026-58503
Vulnerability Analysis
The vulnerability resides in the reset_password function in frappe/core/doctype/user/user.py. The endpoint is decorated with @frappe.whitelist(allow_guest=True, methods=["POST"]), meaning it accepts unauthenticated POST requests. Prior to the fix, the function returned four distinguishable outcomes: "not found" (via a 404 HTTP status code from frappe.DoesNotExistError), "not allowed" (Administrator account), "disabled" (disabled user), and a success message when the reset email was dispatched. The client-side JavaScript in login.js explicitly mapped each response to a distinct user-facing message. An attacker scripting requests against the endpoint can distinguish valid enabled accounts from invalid, disabled, or privileged accounts based on the HTTP status code and response body.
Root Cause
The root cause is an observable response discrepancy [CWE-203]. The password reset workflow prioritized descriptive user feedback over uniform response semantics, leaking account state through both message content and HTTP status codes. Rate limiting via @rate_limit(limit=get_password_reset_limit, seconds=60 * 60) was in place but did not prevent enumeration at low request rates.
Attack Vector
The attack is executed remotely over the network without authentication or user interaction. An attacker submits candidate usernames or email addresses in POST requests to /api/method/frappe.core.doctype.user.user.reset_password and parses the response to classify each identifier. The technique supports building account lists for follow-on password spraying, phishing, or targeted social engineering campaigns.
# Patched reset_password function in frappe/core/doctype/user/user.py
@frappe.whitelist(allow_guest=True, methods=["POST"])
@rate_limit(limit=get_password_reset_limit, seconds=60 * 60)
def reset_password(user: str) -> None:
# Always return the same generic response regardless of whether the user
# exists, is disabled, or is restricted. This prevents username enumeration
# via different messages or HTTP status codes (CWE-204).
try:
user_doc: User = frappe.get_doc("User", user)
if user_doc.name != "Administrator" and user_doc.enabled:
user_doc.validate_reset_password()
user_doc._reset_password(send_email=True)
# For Administrator or disabled users: silently skip — same response below
except frappe.DoesNotExistError:
frappe.local.response["http_status_code"] = 404
Source: GitHub Commit d3becf5 — patches remove differentiated return values ("not allowed", "disabled") and unify the response for all account states.
Detection Methods for CVE-2026-58503
Indicators of Compromise
- High-volume POST requests to /api/method/frappe.core.doctype.user.user.reset_password from single or distributed source IPs.
- Sequential or dictionary-style username submissions to the reset endpoint within short time windows.
- HTTP 404 responses from the reset endpoint clustered against many distinct user parameter values.
- Reset requests originating from IP ranges associated with proxies, VPNs, or known scanning infrastructure.
Detection Strategies
- Baseline normal password reset request volumes per hour and alert on statistical deviations.
- Correlate reset endpoint activity with subsequent authentication failures to identify enumeration followed by credential attacks.
- Inspect web server and reverse proxy logs for unique user parameter values submitted per source IP over rolling windows.
- Flag any client that receives more than a small number of 404 responses from the reset endpoint.
Monitoring Recommendations
- Enable structured access logging for the Frappe /api/method/ route family with full request body capture where compliant.
- Forward web tier logs to a centralized analytics platform for cross-source correlation and long-term retention.
- Alert on password reset volume anomalies broken down by source IP, ASN, and geographic region.
- Monitor for reset attempts targeting the Administrator account name, which should never legitimately occur through this endpoint.
How to Mitigate CVE-2026-58503
Immediate Actions Required
- Upgrade Frappe to version 15.106.0 or 16.16.0 depending on your current major version track.
- Audit web logs for prior enumeration activity against the reset_password endpoint and identify any harvested account lists.
- Rotate credentials and enforce MFA on any accounts that may have been targeted following an observed enumeration campaign.
- Restrict access to the reset endpoint through a WAF rule or reverse proxy filter until patching is complete.
Patch Information
Frappe published fixes in GitHub Release v15.106.0 and GitHub Release v16.16.0. The corresponding commits 1ff64d4 and d3becf5 normalize the reset_password return value to None and unify the client-side status message to "Instructions Emailed" for all cases. Full details are documented in GitHub Security Advisory GHSA-3vqc-c545-w7jg.
Workarounds
- Deploy a WAF or reverse proxy rule that rewrites all reset_password responses to a uniform status code and body until the patch is applied.
- Tighten the rate limit configuration by lowering the value returned by get_password_reset_limit to reduce enumeration throughput.
- Place the Frappe application behind an authenticated portal or CAPTCHA challenge on the forgot-password flow for internal-only deployments.
# Upgrade Frappe using the bench CLI
bench update --reset
bench switch-to-branch version-15 frappe --upgrade # for 15.x track
# or
bench switch-to-branch version-16 frappe --upgrade # for 16.x track
bench pip install -e apps/frappe
bench migrate
bench restart
# Verify installed version
bench version | grep frappe
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

