SECURITY ADVISORY / 01

CVE-2026-66058 Exploit & Vulnerability Analysis

Complete CVE-2026-66058 security advisory with proof of concept (PoC), exploit details, and patch analysis for frappe.

frappe products NVD ↗
Exploit PoC Vulnerability Patch Analysis

The Exploit

An authenticated user with any role can follow documents they should not have read access to, and receive change notifications for restricted data.

POST /api/resource/Document%20Follow HTTP/1.1
Host: frappe.example.com
Content-Type: application/json
Authorization: Bearer <valid_session_token>

{
  "ref_doctype": "Salary Structure",
  "ref_docname": "EMP-2024-001",
  "user": "[email protected]"
}

The server responds with HTTP 200 and a Document Follow record is created. The attacker's account is now following a salary structure document, and will receive email notifications whenever that document is updated—including sensitive fields like ctc and base—even though the attacker's role grants no read permission on the Salary Structure doctype. The follow persists across sessions and the attacker continues receiving notifications until the record is manually deleted by an administrator.

What the Patch Did

Before:

def add_document_follow(doctype, doc_name, user):
	if user != frappe.session.user and not frappe.has_permission("Document Follow", "write"):
		frappe.throw(_("You can only follow documents for yourself."), frappe.PermissionError)

	if not frappe.db.get_value("User", user, "document_follow_notify", ignore=True, cache=True):
		frappe.toast(_("Document follow is not enabled for this user."))
		return False

After:

def add_document_follow(doctype, doc_name, user):
	if user != frappe.session.user and not frappe.has_permission("Document Follow", "write"):
		frappe.throw(_("You can only follow documents for yourself."), frappe.PermissionError)

	if not frappe.has_permission(doctype, "read", doc=doc_name, user=user):
		frappe.throw(_("You do not have permission to access this document."), frappe.PermissionError)

	if not frappe.db.get_value("User", user, "document_follow_notify", ignore=True, cache=True):
		frappe.toast(_("Document follow is not enabled for this user."))
		return False

The patch added a document-level permission check using frappe.has_permission(doctype, "read", doc=doc_name, user=user). This function validates whether the calling user (or the target user if different) holds explicit read permission on the specific document before allowing the follow relationship to be created. The check happens before any notification setting is queried, preventing the creation of follow records for inaccessible documents.

The patch also modified get_message_for_user() to filter out stale follows:

Before:

for document_follow in latest_document_follows:
	content = get_message(document_follow.ref_docname, document_follow.ref_doctype, frequency, user)
	if content:
		message = message + content

After:

for document_follow in latest_document_follows:
	if not frappe.has_permission(
		document_follow.ref_doctype, "read", doc=document_follow.ref_docname, user=user
	):
		frappe.db.delete(
			"Document Follow",
			{
				"ref_doctype": document_follow.ref_doctype,
				"ref_docname": document_follow.ref_docname,
				"user": user,
			},
		)
		continue
	content = get_message(document_follow.ref_docname, document_follow.ref_doctype, frequency, user)
	if content:
		message = message + content

This defense-in-depth control re-validates permissions at notification time and removes invalid follow records, preventing information leakage even if stale follows existed.

Root Cause

CWE-862: Missing Authorization. The update_follow endpoint (called by add_document_follow) accepted the doctype and doc_name parameters from the HTTP request without verifying that the authenticated user held read permission on the target document. The function checked whether the user could manage Document Follow records themselves, but did not gate access based on the document being followed. This trust boundary violation allowed any authenticated user to subscribe to change notifications for any document in the system, bypassing role-based access control (RBAC). The attacker's document reference travels from the JSON request body into the doctype and doc_name variables, flows directly into the follow record creation query, and crosses the authorization boundary without validation.

Why It Works

The load-bearing line is frappe.has_permission(doctype, "read", doc=doc_name, user=user). Without it, an authenticated user can craft a follow request for any doctype and docname pair, and the server accepts it because it only checks whether the user can manage Document Follow records (a metadata permission), not whether they can read the target document itself. If you remove that single line, the bug remains fully exploitable; any follow request succeeds. The engineers added the duplicate check in get_message_for_user() because permissions can change after a follow is created (a user's role can be revoked, or a document can be made private)—this reactive filter cleans up orphaned follows and prevents leakage during the notification window. Without the proactive check at creation time, however, the reactive cleanup is mere damage control.

Hardening Checklist

  • Always validate document-level permissions before creating relationships. If a user can "follow," "star," "subscribe to," or "tag" a document, call frappe.has_permission(doctype, "read", doc=doc_name) before persisting the relationship, not just after. Do not assume that metadata permissions (like "can manage Document Follow") imply data access.

  • Implement re-validation filters in notification and export workflows. When iterating over user-created subscriptions or preferences, re-check permissions before including data in responses or background jobs. Use the same permission check at both creation and consumption time to defend against role changes.

  • Audit all endpoints that accept document references as parameters. Grep for frappe.call, frappe.db.get_value, or HTTP handlers that take doctype and doc_name as input. Ensure each one calls frappe.has_permission(doctype, "read", doc=...) or equivalent before returning or processing the document.

  • Test permission bypasses with restricted roles. Create a test user with no access to a sensitive doctype (e.g., Salary Structure, Bank Account). Attempt to follow, star, share, or export that document via the API. If the request succeeds, the permission check is missing.

  • Log all follow/subscription creation attempts. Include the user, target doctype, and target docname in audit logs. This enables rapid detection if an attacker mass-follows documents before permissions are re-validated.

References

  • https://nvd.nist.gov/vuln/detail/CVE-2026-66058

Frequently asked questions about CVE-2026-66058

What is CVE-2026-66058?

CVE-2026-66058 is a security vulnerability identified in frappe. This security advisory provides detailed technical analysis of the vulnerability, exploit methodology, affected versions, and complete remediation guidance.

Is there a PoC (proof of concept) for CVE-2026-66058?

Yes. This writeup includes proof-of-concept details and a technical exploit breakdown for CVE-2026-66058. Review the analysis sections above for the PoC walkthrough and code examples.

How does CVE-2026-66058 get exploited?

The technical analysis section explains the vulnerability mechanics, attack vectors, and exploitation methodology affecting frappe. PatchLeaks publishes this information for defensive and educational purposes.

What products and versions are affected by CVE-2026-66058?

CVE-2026-66058 affects frappe. Check the affected-versions section of this advisory for specific version ranges, vulnerable configurations, and compatibility information.

How do I fix or patch CVE-2026-66058?

The patch analysis section provides guidance on updating to patched versions, applying workarounds, and implementing compensating controls for frappe.

What is the CVSS score for CVE-2026-66058?

The severity rating and CVSS scoring for CVE-2026-66058 affecting frappe is documented in the vulnerability details section. Refer to the NVD entry for the current authoritative score.