Problem
get_doctype_fields is decorated with allow_guest=True, which means any unauthenticated caller can pass an arbitrary DocType name and receive its full field list. This exposes schema for sensitive system DocTypes (User, System Settings, etc.) to anyone who knows the API endpoint.
Proposed Fix
Validate that the requested doctype is the linked_doctype of at least one published Form before serving the response to guest callers. Authenticated users (form builders) are exempt from this check.
@frappe.whitelist(allow_guest=True)
def get_doctype_fields(doctype: str, form_id: str | None = None) -> dict:
if frappe.session.user == "Guest":
filters = {"linked_doctype": doctype, "is_published": 1}
if form_id:
filters["name"] = form_id
if not frappe.db.exists("Form", filters):
frappe.throw(_("Not permitted"), frappe.PermissionError)
...
Passing form_id is optional but tightens the check further — a guest must prove they're loading a specific published form, not just probing doctype names.
What this achieves
- Guests can only see fields for doctypes tied to a live, published form
- Form builders (authenticated users) are completely unaffected
- No breaking changes to the existing frontend flow; the submission page would pass
form_id alongside doctype
Out of scope / future work
- Per-IP rate limiting to prevent enumeration of valid published form doctypes (can be added via Frappe's
RateLimiter)
Problem
get_doctype_fieldsis decorated withallow_guest=True, which means any unauthenticated caller can pass an arbitrary DocType name and receive its full field list. This exposes schema for sensitive system DocTypes (User,System Settings, etc.) to anyone who knows the API endpoint.Proposed Fix
Validate that the requested
doctypeis thelinked_doctypeof at least one published Form before serving the response to guest callers. Authenticated users (form builders) are exempt from this check.Passing
form_idis optional but tightens the check further — a guest must prove they're loading a specific published form, not just probing doctype names.What this achieves
form_idalongsidedoctypeOut of scope / future work
RateLimiter)