
ERPNext 16.16.0 - Stored XSS in POS cart item rendering
4,8
Medium
Detected by

Fluid Attacks AI SAST Scanner
Disclosed by
Oscar Naveda
Summary
Full name
ERPNext 16.16.0 - Stored XSS in POS cart item rendering
Code name
State
Public
Release date
Affected product
ERPNext
Vendor
Frappe
Affected version(s)
16.16.0
Vulnerability name
Stored cross-site scripting (XSS)
Vulnerability type
Remotely exploitable
Yes
CVSS v4.0 vector string
CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:P/VC:L/VI:L/VA:N/SC:L/SI:L/SA:N
CVSS v4.0 base score
4.8
Exploit available
Yes
CVE ID(s)
Description
An authenticated ERPNext user with Item record edit permissions (e.g. Item Manager, Stock Manager) can persist arbitrary HTML/JavaScript in the item_name, description, or image fields of an Item and trigger unescaped rendering in the Point of Sale (POS) cart interface for every operator who adds that item to a transaction.
The sink is render_cart_item in erpnext/selling/page/point_of_sale/pos_item_cart.js:611, which constructs an HTML string using template literals that directly embed item_data.item_name, the output of get_description_html(), and the output of get_item_image_html(), then passes the result to jQuery's .html(). The description field has a conditional sanitization routine that is trivially bypassed: it only activates when the content contains the literal string <div>, leaving all other HTML tags (e.g. <img onerror=...>, <script>) unhandled. item_data.image is inserted directly into an img src attribute without escaping.
Vulnerability
Source → sink path
Source — user-controlled Item fields (
itemschild table of POS Invoice):Item fields
item_name,description, andimageare stored in theItemdoctype and propagate into the POS invoiceitemstable. They are accessible to the client viathis.events.get_frm().doc.itemswith no transformation. Any user holding a role that permits Item creation or editing (Item Manager, Stock Manager, Purchase Manager, and similar) can set these fields to arbitrary HTML/JS payloads.Sink — jQuery
.html()with unescaped template literals (pos_item_cart.js:611-620):jQuery's
.html()is aninnerHTMLsetter. HTML tags present in any interpolated value are parsed and executed by the browser.item_data.item_nameis embedded with no sanitization whatsoever.Bypassed sanitization on
description(pos_item_cart.js:661-677):The guard condition checks only for the exact lowercase string
"<div>". A payload such as<img src=x onerror=alert(1)>does not contain<div>, so the entire sanitization block is skipped.frappe.ellipsisis a pure truncation helper (not an HTML-escaping utility); a payload of ≤ 45 characters passes through unchanged and is injected into the DOM.Even when the
<div>branch is entered, thecatchfallback only replaces<div>and</div>tags while leaving all other HTML intact, providing no protection against<script>,<img>,<svg>, or event-handler attributes.imageinjected intosrcattribute without escaping (pos_item_cart.js:681-687):imageis interpolated directly into thesrcattribute. Additionally, the template contains a stray double-quote afteralt="${frappe.get_abbr(item_name)}", producing malformed HTML (alt="AB""). Withimage = '" onerror="alert(1)':Produces:
src="" onerror="alert(1)"">The
onerrorevent handler breaks out of thesrcattribute context and executes.
escape()fordata-row-nameprovides no HTML protection (pos_item_cart.js:605):escape()iswindow.escape()(deprecated URI-encoding). It does not encode<,>, or". It is applied only todata-row-name, not to any of the displayed fields.
Relevant code:
erpnext/selling/page/point_of_sale/pos_item_cart.js:599-692(render_cart_item)erpnext/selling/page/point_of_sale/pos_item_cart.js:611(jQuery.html()sink)erpnext/selling/page/point_of_sale/pos_item_cart.js:614-616(item_namedirect interpolation)erpnext/selling/page/point_of_sale/pos_item_cart.js:661-677(get_description_html, bypassed sanitization)erpnext/selling/page/point_of_sale/pos_item_cart.js:679-691(get_item_image_html,srcinjection)erpnext/selling/page/point_of_sale/pos_item_cart.js:605(escape()misuse ondata-row-name)
PoC
Reproduction used in validation environment
Environment: ERPNext 16.16.0 running locally.
Attack path — via Item record fields (user with Item edit permissions)
Log in to ERPNext as a user with Item Manager or Stock Manager role.
Navigate to
Stock → Items → <any Item> → Edit(or create a new Item).Set the Description field to a payload that does not contain
<div>(to bypass the guard condition):This payload is 38 characters, within the 45-character ellipsis limit, and contains no
<div>, so it bypassesget_description_htmlentirely.Alternatively, set Item Name to:
Save the Item.
Confirm the payload was stored verbatim:
Open the POS interface as a POS operator (a different user/session):
Navigate to
Point of Sale→ open any POS session → add the malicious Item to the cart.render_cart_itemis called,get_description_htmlskips the sanitization block (no<div>in the payload),frappe.ellipsistruncates without escaping, and the payload reaches$item_to_update.html(). Theonerrorevent fires and executesalert(document.cookie)in the operator's browser.
Alternate path via image attribute injection:
Set the Item's image field to:
When the Item is added to the cart, get_item_image_html renders:
The first onerror attribute is overridden or both fire depending on browser behavior; in either case the injected handler executes.
Evidence of Exploitation
Video of exploitation:
Static Evidence:

Our security policy
We have reserved the ID CVE-2026-42839 to refer to this issue from now on.
System Information
ERPNext
Version 16.16.0 (branch: develop)
Operating System: Any
References
Github Repository: https://github.com/frappe/erpnext
Mitigation
There is currently no patch available for this vulnerability.
Credits
The vulnerability was discovered by Oscar Naveda from Fluid Attacks' Offensive Team using the AI SAST Scanner.
Timeline
Vulnerability discovered
Vendor contacted
Public disclosure
Does your application use this vulnerable software?
During our free trial, our tools assess your application, identify vulnerabilities, and provide recommendations for their remediation.













